Pandas: Tidak ada cara untuk membangun dtype DataFrame campuran tanpa salinan total, solusi yang diusulkan

Dibuat pada 9 Jan 2015  ·  58Komentar  ·  Sumber: pandas-dev/pandas

Setelah berjam-jam mengacak-acak rambut saya, saya sampai pada kesimpulan bahwa tidak mungkin membuat dtype DataFrame campuran tanpa menyalin semua datanya. Artinya, apa pun yang Anda lakukan, jika Anda ingin membuat dtype DataFrame campuran , Anda pasti akan membuat versi data sementara (misalnya menggunakan np.empty), dan berbagai konstruktor DataFrame akan selalu membuat salinan sementara ini. Masalah ini telah diangkat, setahun yang lalu: https://github.com/pydata/pandas/issues/5902.

Ini sangat buruk untuk interoperabilitas dengan bahasa pemrograman lain. Jika Anda berencana untuk mengisi data di DataFrame dari misalnya panggilan ke C, cara termudah untuk melakukannya sejauh ini adalah membuat DataFrame dengan python, dapatkan pointer ke data yang mendasarinya, yaitu np.arrays, dan berikan np ini .array bersama sehingga mereka dapat diisi. Dalam situasi ini, Anda tidak peduli dengan data apa DataFrame dimulai, tujuannya hanya untuk mengalokasikan memori sehingga Anda tahu apa yang Anda salin.

Ini juga umumnya membuat frustrasi karena ini menyiratkan bahwa pada prinsipnya (bergantung pada situasi tertentu, dan spesifik implementasi, dll) sulit untuk menjamin bahwa Anda tidak akan menggunakan dua kali memori yang seharusnya Anda gunakan.

Ini memiliki solusi yang sangat sederhana yang sudah didasarkan pada tumpukan python kuantitatif: memiliki metode yang analog dengan numpy's kosong. Ini mengalokasikan ruang, tetapi sebenarnya tidak membuang waktu untuk menulis atau menyalin apa pun. Karena kosong sudah diambil, saya akan mengusulkan untuk memanggil metode from_empty. Itu akan menerima indeks (wajib, kasus penggunaan paling umum adalah melewati np.arange(N)), kolom (wajib, biasanya daftar string), tipe (daftar tipe yang dapat diterima untuk kolom, panjang yang sama dengan kolom). Daftar tipe harus menyertakan dukungan untuk semua tipe numerik numpy (int, float), serta kolom Pandas khusus seperti DatetimeIndex dan Categorical.

Sebagai bonus tambahan, karena implementasinya dalam metode yang sepenuhnya terpisah, itu tidak akan mengganggu API yang ada sama sekali.

API Design Constructors Dtypes

Komentar yang paling membantu

Ada banyak utas di SO yang meminta fitur ini.

Sepertinya saya bahwa semua masalah ini berasal dari BlockManager yang mengkonsolidasikan kolom terpisah menjadi satu potongan memori ('blok').
Bukankah perbaikan termudah adalah tidak mengkonsolidasikan data ke dalam blok ketika copy=False ditentukan.

Saya memiliki BlockManager yang tidak ditambal monyet:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
bahwa saya digunakan untuk mengatasi masalah ini.

Semua 58 komentar

Anda cukup membuat bingkai kosong dengan indeks dan kolom
kemudian tetapkan ndarrays - ini tidak akan menyalin Anda menetapkan semua tipe-d tertentu sekaligus

Anda dapat membuatnya dengan np.empty jika Anda mau

df = pd.DataFrame(index=range(2), columns=["dude", "wheres"])

df
Out[12]:
  dude wheres
0  NaN    NaN
1  NaN    NaN

x = np.empty(2, np.int32)

x
Out[14]: array([6, 0], dtype=int32)

df.dude = x

df
Out[16]:
   dude wheres
0     6    NaN
1     0    NaN

x[0] = 0

x
Out[18]: array([0, 0], dtype=int32)

df
Out[19]:
   dude wheres
0     6    NaN
1     0    NaN

Sepertinya itu meniru saya. Kecuali kode yang saya tulis bukan yang Anda maksud, atau penyalinan yang terjadi bukanlah salinan yang Anda pikir saya coba hindari.

Anda mengubah tipe d
itu sebabnya disalin coba dengan pelampung

y = np.empty(2, np.float64)

df
Out[21]:
   dude wheres
0     6    NaN
1     0    NaN

df.wheres = y

y
Out[23]: array([  2.96439388e-323,   2.96439388e-323])

y[0] = 0

df
Out[25]:
   dude         wheres
0     6  2.964394e-323
1     0  2.964394e-323

df = pd.DataFrame(index=range(2), columns=["dude", "wheres"])

df.dtypes
Out[27]:
dude      object
wheres    object
dtype: object

Dtype adalah objek, jadi itu berubah terlepas dari apakah saya menggunakan float atau int.

In [25]: arr = np.ones((2,3))

In [26]: df = DataFrame(arr,columns=['a','b','c'])

In [27]: arr[0,1] = 5

In [28]: df
Out[28]: 
   a  b  c
0  1  5  1
1  1  1  1

Membuat salinan tanpa salinan pada tipe campuran dapat dilakukan tetapi cukup rumit. Masalahnya adalah beberapa jenis memerlukan salinan (misalnya objek untuk menghindari masalah pertikaian memori). Dan struktur internal menggabungkan tipe yang berbeda, jadi menambahkan tipe baru akan memerlukan salinan. Menghindari salinan cukup sulit dalam banyak kasus.

Anda hanya perlu membuat apa yang Anda butuhkan, mendapatkan pointer ke data dan kemudian menimpanya. Mengapa itu menjadi masalah?

Masalahnya adalah untuk membuat apa yang saya butuhkan, saya harus menyalin hal-hal dengan tipe d yang benar, data yang tidak ingin saya gunakan. Bahkan dengan asumsi bahwa saran Anda untuk membuat DataFrame kosong tidak menggunakan RAM yang signifikan, ini tidak mengurangi biaya penyalinan. Jika saya ingin membuat DataFrame 1 gigabyte dan mengisinya di tempat lain, saya harus membayar biaya menyalin gigabyte sampah di sekitar memori, yang sama sekali tidak perlu. Apakah Anda tidak melihat ini sebagai masalah?

Ya, saya mengerti bahwa struktur internal mengkonsolidasikan berbagai jenis. Saya tidak yakin persis apa yang Anda maksud dengan masalah pertikaian memori, tetapi bagaimanapun juga objek bukanlah yang menarik di sini.

Sebenarnya, walaupun menghindari salinan secara umum adalah masalah yang sulit, menghindarinya dengan cara yang saya sarankan cukup mudah karena saya menyediakan semua informasi yang diperlukan sejak awal. Ini identik dengan membangun dari data, kecuali bahwa alih-alih menyimpulkan dtypes dan # baris dari data dan menyalin data, Anda menentukan dtypes dan # baris secara langsung, dan melakukan segala sesuatu yang lain persis seperti yang akan Anda lakukan minus salinan.

Anda memerlukan konstruktor "kosong" untuk setiap jenis kolom yang didukung. Untuk tipe numerik numpy, ini jelas, perlu pekerjaan yang tidak nol untuk Categorical, tidak yakin tentang DatetimeIndex.

meneruskan dict ke konstruktor dan copy=False seharusnya berfungsi

Jadi ini akan berhasil. Tetapi Anda harus PASTI bahwa array yang Anda lewati adalah tipe d yang berbeda. Dan begitu Anda melakukan sesuatu untuk ini, itu bisa menyalin data yang mendasarinya. Jadi YMMV. Anda tentu saja dapat memasukkan np.empty alih-alih yang/nol seperti saya.

In [75]: arr = np.ones((2,3))

In [76]: arr2 = np.zeros((2,2),dtype='int32')

In [77]: df = DataFrame(arr,columns=list('abc'))

In [78]: df2 = DataFrame(arr2,columns=list('de'))

In [79]: result = pd.concat([df,df2],axis=1,copy=False)

In [80]: arr2[0,1] = 20

In [81]: arr[0,1] = 10

In [82]: result
Out[82]: 
   a   b  c  d   e
0  1  10  1  0  20
1  1   1  1  0   0

In [83]: result._data
Out[83]: 
BlockManager
Items: Index([u'a', u'b', u'c', u'd', u'e'], dtype='object')
Axis 1: Int64Index([0, 1], dtype='int64')
FloatBlock: slice(0, 3, 1), 3 x 2, dtype: float64
IntBlock: slice(3, 5, 1), 2 x 2, dtype: int32

In [84]: result._data.blocks[0].values.base
Out[84]: 
array([[  1.,  10.,   1.],
       [  1.,   1.,   1.]])

In [85]: result._data.blocks[1].values.base
Out[85]: 
array([[ 0, 20],
       [ 0,  0]], dtype=int32)

_Upaya awal dihapus karena tidak berhasil karena reindex memaksa casting, yang merupakan "fitur" yang aneh_

Harus menggunakan 'metode', yang membuat upaya ini sedikit kurang memuaskan:

arr = np.empty(1, dtype=[('x', np.float), ('y', np.int)])
df = pd.DataFrame.from_records(arr).reindex(np.arange(100))

Jika Anda benar-benar khawatir tentang kinerja, saya tidak yakin mengapa seseorang tidak menggunakan numpy sebanyak mungkin karena secara konseptual jauh lebih sederhana.

jreback, terima kasih atas solusi Anda. Ini sepertinya berhasil, bahkan untuk Kategoris (yang mengejutkan saya). Jika saya mengalami masalah, saya akan memberi tahu Anda. Saya tidak yakin apa yang Anda maksud dengan: jika Anda melakukan sesuatu untuk ini, itu bisa menyalin. Apa yang kamu maksud dengan sesuatu? Kecuali ada semantik COW, saya akan berpikir apa yang Anda lihat adalah apa yang Anda dapatkan sehubungan dengan salinan dalam vs dangkal, pada waktu konstruksi.

Saya masih berpikir konstruktor from_empty harus diimplementasikan, dan saya tidak berpikir itu akan sesulit itu, sementara teknik ini berfungsi, itu memang melibatkan banyak kode overhead. Pada prinsipnya ini dapat dilakukan dengan menentukan tipe komposit tunggal dan sejumlah baris.

bashtage, solusi ini masih menulis ke seluruh DataFrame. Karena menulis umumnya lebih lambat daripada membaca, ini berarti paling-paling menghemat kurang dari setengah overhead yang dimaksud.

Jelas jika saya belum pergi dan menggunakan numpy, itu karena panda memiliki banyak fitur dan kemampuan luar biasa yang saya sukai, dan saya tidak ingin menyerah. Apakah Anda benar-benar bertanya, atau hanya menyiratkan bahwa saya harus menggunakan numpy jika saya tidak ingin menerima pukulan kinerja ini?

Gores ini, tolong, kesalahan pengguna, dan permintaan maaf saya. reindex_axis dengan copy=False bekerja dengan sempurna.

bashtage, solusi ini masih menulis ke seluruh DataFrame. Karena menulis umumnya lebih lambat daripada membaca, ini berarti paling-paling menghemat kurang dari setengah overhead yang dimaksud.

Benar, tetapi semua yang Anda butuhkan untuk method untuk reindex yang tidak akan diisi dengan apa pun dan kemudian Anda dapat mengalokasikan array yang diketik dengan tipe kolom arbitrer tanpa menulis/menyalin.

Jelas jika saya belum pergi dan menggunakan numpy, itu karena panda memiliki banyak fitur dan kemampuan luar biasa yang saya sukai, dan saya tidak ingin menyerah. Apakah Anda benar-benar bertanya, atau hanya menyiratkan bahwa saya harus menggunakan numpy jika saya tidak ingin menerima pukulan kinerja ini?

Itu agak retoris - meskipun juga merupakan saran serius dari sudut pandang kinerja karena numpy membuatnya lebih mudah untuk mendekati akses data-sebagai-gumpalan-memori yang penting jika Anda mencoba menulis sangat kode kinerja tinggi. Anda selalu dapat mengonversi dari numpy ke panda ketika kesederhanaan kode lebih penting daripada kinerja.

Aku mengerti apa yang kamu ucapkan. Saya masih berpikir itu harus lebih bersih menjadi bagian dari antarmuka daripada solusi, tetapi sebagai solusi, itu bagus dan mudah diimplementasikan.

Pandas tetap menekankan kinerja sebagai salah satu tujuan utamanya. Jelas itu memiliki fitur level yang lebih tinggi dibandingkan dengan numpy, dan itu harus dibayar. Apa yang kita bicarakan tidak ada hubungannya dengan fitur tingkat yang lebih tinggi, dan tidak ada alasan mengapa seseorang harus membayar untuk salinan besar di tempat-tempat di mana Anda tidak membutuhkannya. Saran Anda akan tepat jika seseorang membuat bau tentang biaya pengaturan kolom, indeks, dll, yang sama sekali berbeda dari diskusi ini.

Saya pikir Anda melebih-lebihkan biaya penulisan vs. kode alokasi memori dengan Python -- bagian yang mahal adalah alokasi memori. Pembuatan objek juga mahal.

Keduanya mengalokasikan memori 1GB, satu kosong dan satu nol.

%timeit np.empty(1, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 2.44 µs per loop

%timeit np.zeros(1, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 2.47 µs per loop

%timeit np.zeros(50000000, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 11.7 µs per loop

%timeit np.empty(50000000, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 11.4 µs per loop

3µs untuk zeroing 150.000.000 nilai.

Sekarang bandingkan ini untuk DataFrame sepele.

%timeit pd.DataFrame([[0]])
1000 loops, best of 3: 426 µs per loop

Sekitar 200 kali lebih lambat untuk hal sepele. Tapi itu jauh lebih buruk untuk array yang lebih besar.

%timeit pd.DataFrame(np.empty((50000000, 3)),copy=False)
1 loops, best of 3: 275 ms per loop

Sekarang dibutuhkan 275 m s -- perhatikan bahwa ini tidak menyalin apa pun. Biayanya adalah dalam menyiapkan indeks, dll yang jelas sangat lambat ketika array tidak terlalu besar.

Ini terasa seperti pengoptimalan prematur bagi saya karena biaya overhead lainnya di panda sangat besar sehingga komponen malloc + filliing mendekati 0 biaya.

Tampaknya jika Anda ingin mengalokasikan apa pun dalam loop ketat itu harus berupa array numpy untuk alasan kinerja.

ok, inilah yang menurut saya harus kita lakukan, @quicknir jika Anda ingin melakukan beberapa perbaikan. 2 masalah.

  • #4464 - ini pada dasarnya mengizinkan dtype majemuk di DataFrame konstruktor dan kemudian berbalik dan memanggil from_records() , yang juga dapat dipanggil jika array yang diteruskan adalah array rec/terstruktur - ini pada dasarnya akan menjadikan from_records jalur pemrosesan array yang direk/terstruktur
  • lewati kata kunci copy= ke from_records
  • from_records kemudian dapat menggunakan concat soln yang saya tunjukkan di atas, daripada memisahkan array ulang, membersihkannya (sebagai rangkaian) dan kemudian menyatukannya kembali (ke dalam blok dtype; bagian ini dilakukan secara internal).

Ini sedikit non-sepele tetapi kemudian akan memungkinkan seseorang untuk memasukkan ndarray yang sudah dibuat (bisa kosong) dengan tipe campuran dengan cukup mudah. Perhatikan bahwa ini kemungkinan (dalam implementasi first pass) hanya menangani (int/float/string). karena datetime/timedelta membutuhkan sanitasi khusus dan akan membuat ini sedikit lebih rumit.

jadi @bashtage benar dari perspektif perf. Sangat masuk akal untuk hanya membuat bingkai seperti yang Anda inginkan kemudian memodifikasi ndarrays (tetapi Anda HARUS melakukan ini dengan mengambil blok, jika tidak, Anda akan mendapatkan salinan).

Yang saya maksud di atas adalah ini. Panda mengelompokkan semua tipe-d suka (mis. int64,int32 berbeda) ke dalam 'blok' (2-d dalam bingkai). Ini adalah ndarray memori yang berdekatan (yang baru dialokasikan, kecuali jika dilewatkan begitu saja yang saat ini hanya berfungsi untuk satu tipe d). Jika Anda kemudian melakukan setitem, misalnya df['new_columns'] = 5 dan Anda sudah memiliki blok int64, maka kolom baru ini pada akhirnya akan digabungkan ke dalamnya (menghasilkan alokasi memori baru untuk tipe-d itu). Jika Anda menggunakan referensi sebagai pandangan tentang ini, itu tidak akan valid lagi. Itulah mengapa ini bukan strategi yang dapat Anda terapkan tanpa mengintip internal DataFrame.

@bashtage yeh biaya besar adalah indeks seperti yang telah Anda catat. a RangeIndex (lihat #939) akan menyelesaikan masalah ini sepenuhnya. (sebenarnya hampir selesai di cabang samping, hanya perlu dibersihkan).

Bahkan dengan RangeIndex dioptimalkan masih akan 2 kali lipat lebih lambat daripada membangun array NumPy, yang cukup adil mengingat sifat bobot yang jauh lebih berat dan kemampuan tambahan dari DataFrame .

Saya pikir ini hanya dapat dianggap sebagai fungsi kenyamanan, dan bukan masalah kinerja. Mungkin berguna untuk menginisialisasi jenis campuran DataFrame atau Panel seperti.

dtype=np.dtype([('GDP', np.float64), ('Population', np.int64)])
pd.Panel(items=['AU','AT'],
         major_axis=['1972','1973'],
         minor_axis=['GDP','Population'], 
         dtype=[np.float, np.int64])

ini hanya masalah API/kenyamanan

setuju perf benar-benar masalah insidental (dan bukan pengemudi)

@bashtage

%timeit pd.DataFrame(np.kosong((100, 1000000)))
100 loop, terbaik dari 3: 15,6 ms per loop

%timeit pd.DataFrame(np.empty((100, 1000000)), copy=True)
1 loop, terbaik dari 3: 302 ms per loop

Jadi menyalin ke dalam kerangka data tampaknya memakan waktu 20 kali lebih lama daripada semua pekerjaan lain yang terlibat dalam membuat DataFrame, yaitu penyalinan (dan alokasi tambahan) adalah 95% dari waktu. Tolok ukur yang Anda lakukan tidak mengukur hal yang benar. Apakah salinan itu sendiri atau alokasi yang memakan waktu tidak terlalu penting, intinya adalah jika saya dapat menghindari salinan untuk beberapa dtype DataFrame seperti yang saya bisa untuk satu dtype DataFrame, saya dapat menghemat banyak waktu.

Alasan dua urutan besarnya Anda juga menipu. Ini bukan satu-satunya operasi yang dilakukan, ada operasi lain yang dilakukan yang membutuhkan waktu, seperti pembacaan disk. Saat ini, salinan tambahan yang perlu saya lakukan untuk membuat DataFrame memakan waktu sekitar separuh waktu dalam program sederhana saya yang hanya membaca data dari disk dan menjadi DataFrame. Jika membutuhkan waktu 1/20 th, maka pembacaan disk akan menjadi dominan (sebagaimana mestinya) dan perbaikan lebih lanjut hampir tidak akan berpengaruh.

Jadi saya ingin sekali lagi menekankan kepada Anda berdua: ini adalah masalah kinerja yang nyata.

jreback, mengingat bahwa strategi penggabungan tidak berfungsi untuk Kategoris, jangan berpikir bahwa peningkatan yang Anda sarankan di atas akan berhasil. Saya pikir titik awal yang lebih baik adalah pengindeksan ulang. Masalahnya sekarang adalah bahwa pengindeksan ulang melakukan banyak hal tambahan. Namun pada prinsipnya, DataFrame dengan baris nol memiliki semua informasi yang diperlukan untuk memungkinkan pembuatan DataFrame dengan jumlah baris yang benar, tanpa melakukan pekerjaan yang tidak perlu. Btw, ini membuat saya benar-benar merasa panda membutuhkan objek skema, tapi itu diskusi untuk hari lain.

Saya pikir kita harus setuju untuk tidak setuju. IMO DataFrames bukan objek kinerja ekstrem dalam ekosistem numerik, seperti yang ditunjukkan oleh urutan perbedaan besar antara array numpy dasar dan pembuatan DataFrame.

%timeit np.empty((1000000, 100))
1000 loops, best of 3: 1.61 ms per loop

%timeit pd.DataFrame(np.empty((1000000,100)))
100 loops, best of 3: 15.3 ms per loop

Saat ini, salinan tambahan yang perlu saya lakukan untuk membuat DataFrame memakan waktu sekitar separuh waktu dalam program sederhana saya yang hanya membaca data dari disk dan menjadi DataFrame. Jika membutuhkan waktu 1/20 th, maka pembacaan disk akan menjadi dominan (sebagaimana mestinya) dan perbaikan lebih lanjut hampir tidak akan berpengaruh.

Saya pikir ini bahkan lebih sedikit alasan untuk peduli dengan kinerja DataFrame -- bahkan jika Anda dapat membuatnya 100% gratis, total waktu program hanya berkurang 50%.

Saya setuju bahwa ada ruang bagi Anda untuk melakukan PR di sini untuk menyelesaikan masalah ini, apakah Anda ingin menganggapnya sebagai masalah kinerja atau sebagai masalah kenyamanan. Dari POV saya, saya melihatnya sebagai yang terakhir karena saya akan selalu menggunakan array numpy ketika saya peduli dengan kinerja. Numpy melakukan hal-hal lain seperti tidak menggunakan manajer blok yang relatif efisien untuk beberapa hal (seperti menumbuhkan array dengan menambahkan kolom). tapi buruk dari sudut pandang lain.

Mungkin ada dua pilihan. Yang pertama, konstruktor kosong seperti pada contoh yang saya berikan di atas. Ini tidak akan menyalin apa pun, tetapi mungkin Null-fill agar konsisten dengan hal-hal lain di panda. Pengisian nol cukup murah dan bukan akar masalah IMO.

Yang lainnya adalah memiliki metode DataFrame.from_blocks yang akan mengambil blok yang telah dibentuk sebelumnya untuk diteruskan langsung ke manajer blok. Sesuatu seperti

DataFrame.from_blocks([np.empty((100,2)), 
                       np.empty((100,3), dtype=np.float32), 
                       np.empty((100,1), dtype=np.int8)],
                     columns=['f8_0','f8_1','f4_0','f4_1','f4_2','i1_0'],
                     index=np.arange(100))

Metode jenis ini akan menegakkan bahwa blok memiliki bentuk yang kompatibel, semua blok memiliki tipe unik, serta pemeriksaan biasa untuk bentuk indeks dan kolom. Jenis metode ini tidak akan melakukan apa pun pada data dan akan menggunakannya di BlockManger.

@quicknir Anda mencoba menggabungkan hal-hal yang cukup rumit. Categorical tidak ada di numpy, melainkan dtype majemuk seperti itu adalah konstruksi panda. Anda harus membangun dan menetapkan kemudian secara terpisah (yang sebenarnya cukup murah - ini tidak digabungkan menjadi blok seperti dtypes tunggal lainnya).

@bashtage soln tampaknya masuk akal. Ini dapat memberikan beberapa pemeriksaan sederhana dan hanya melewati data (dan dipanggil oleh rutinitas internal lainnya). Biasanya pengguna tidak perlu menyibukkan diri dengan perwakilan internal. Karena Anda benar-benar ingin, maka Anda harus menyadari hal ini.

Semua yang dikatakan, saya masih tidak yakin mengapa Anda tidak membuat bingkai persis seperti yang Anda inginkan. Kemudian ambil pointer blok dan ubah nilainya. Biayanya memori yang sama, dan seperti yang ditunjukkan @bashtage, ini cukup murah untuk membuat bingkai nol (yang memiliki semua dtype,index,columns) yang sudah ditetapkan.

Tidak yakin apa yang Anda maksud dengan konstruktor kosong, tetapi jika Anda bermaksud membangun kerangka data tanpa baris dan skema yang diinginkan serta memanggil indeks ulang, ini adalah jumlah waktu yang sama dengan membuat dengan copy=True.

Proposal kedua Anda masuk akal, tetapi hanya jika Anda dapat mengetahui bagaimana melakukan Kategoris. Mengenai hal itu, saya sedang mempelajari kode dan saya menyadari bahwa Kategorikal tidak dapat dikonsolidasikan. Jadi berdasarkan firasat, saya membuat array integer dan dua Seri kategori, saya kemudian membuat tiga DataFrames, dan menggabungkan ketiganya. Benar saja, itu tidak melakukan penyalinan meskipun dua DataFrames memiliki dtype yang sama. Saya akan mencoba melihat bagaimana membuatnya berfungsi untuk Datetime Index.

@jreback Saya masih tidak mengikuti apa yang Anda maksud dengan membuat bingkai persis seperti yang Anda inginkan.

@quicknir mengapa Anda tidak menunjukkan contoh kode/kode semu dari apa yang sebenarnya Anda coba lakukan.

def read_dataframe(filename, ....):
   f = my_library.open(filename)
   schema = f.schema()
   row_count = f.row_count()
   df = pd.DataFrame.from_empty(schema, row_count)
   dict_of_np_arrays = get_np_arrays_from_DataFrame(df)
   f.read(dict_of_np_arrays)
   return df

Kode sebelumnya membuat kamus array numpy terlebih dahulu, dan kemudian membangun DataFrame dari itu karena itu menyalin semuanya. Sekitar separuh waktu dihabiskan untuk itu. Jadi saya mencoba mengubahnya ke skema ini. Masalahnya, membangun df seperti di atas bahkan ketika Anda tidak peduli dengan isinya sangat mahal.

@quicknir dict dari np array membutuhkan banyak penyalinan.

Anda hanya harus melakukan ini:

# construct your biggest block type (e.g. say you have mostly floats)
df = DataFrame(np.empty((....)),index=....,columns=....)

# then add in other things you need (say strings)
df['foo'] = np.empty(.....)

# say ints
df['foo2'] = np.empty(...)

jika Anda melakukan ini dengan dtype, itu akan menjadi murah

kemudian.

for dtype, block in df.as_blocks():
    # fill the values
    block.values[0,0] = 1

karena nilai blok ini dilihat ke dalam array numpy

Komposisi tipe tidak diketahui sebelumnya secara umum, dan dalam kasus penggunaan yang paling umum ada campuran float dan int yang sehat. Saya kira saya tidak mengikuti bagaimana ini akan menjadi murah, jika saya memiliki 30 kolom float dan 10 kolom int, maka ya, float akan sangat murah. Tetapi ketika Anda melakukan int, kecuali ada beberapa cara untuk melakukannya sekaligus yang saya lewatkan, setiap kali Anda menambahkan satu kolom int lagi, itu akan menyebabkan seluruh blok int dialokasikan kembali.

Solusi yang Anda berikan kepada saya sebelumnya hampir berfungsi, sepertinya saya tidak bisa membuatnya berhasil untuk DatetimeIndex.

Tidak yakin apa yang Anda maksud dengan konstruktor kosong, tetapi jika Anda bermaksud membangun kerangka data tanpa baris dan skema yang diinginkan serta memanggil indeks ulang, ini adalah jumlah waktu yang sama dengan membuat dengan copy=True.

Konstruktor kosong akan terlihat seperti

dtype=np.dtype([('a', np.float64), ('b', np.int64), ('c', np.float32)])
df = pd.DataFrame(columns='abc',index=np.arange(100),dtype=dtype)

Ini akan menghasilkan output yang sama dengan

dtype=np.dtype([('a', np.float64), ('b', np.int64), ('c', np.float32)])
arr = np.empty(100, dtype=dtype)
df = pd.DataFrame.from_records(arr, index=np.arange(100))

hanya itu tidak akan menyalin data.

Pada dasarnya konstruktor akan mengizinkan dtype campuran untuk panggilan berikut yang berfungsi tetapi hanya satu dtype dasar.

df = pd.DataFrame(columns=['a','b','c'],index=np.arange(100), dtype=np.float32)

Satu-satunya _feature_ lainnya adalah mencegahnya dari array int yang mengisi null yang memiliki efek samping mengubahnya menjadi objek dtype karena tidak ada nilai yang hilang untuk int.

Proposal kedua Anda masuk akal, tetapi hanya jika Anda dapat mengetahui bagaimana melakukan Kategoris. Mengenai hal itu, saya sedang mempelajari kode dan saya menyadari bahwa Kategorikal tidak dapat dikonsolidasikan. Jadi berdasarkan firasat, saya membuat array integer dan dua Seri kategori, saya kemudian membuat tiga DataFrames, dan menggabungkan ketiganya. Benar saja, itu tidak melakukan penyalinan meskipun dua DataFrames memiliki dtype yang sama. Saya akan mencoba melihat bagaimana membuatnya berfungsi untuk Datetime Index.

Metode from_block harus mengetahui aturan konsolidasi, sehingga memungkinkan beberapa kategorikal, tetapi hanya satu dari tipe dasar lainnya.

ya...ini tidak terlalu sulit untuk dilakukan....mencari seseorang yang ingin memiliki pengenalan yang lembut ke internal..... hint.hint.hint.... :)

Haha, saya bersedia melakukan beberapa pekerjaan implementasi, jangan salah paham. Saya akan mencoba melihat internal akhir pekan ini dan memahami konstruktor mana yang lebih mudah diterapkan. Pertama meskipun saya harus berurusan dengan beberapa masalah DatetimeIndex yang saya alami di utas terpisah.

@quicknir Sudahkah Anda menemukan solusi untuk ini?

Saya mencari cara untuk mengalokasikan (tetapi tidak mengisi) kerangka data tipe campuran dengan murah untuk memungkinkan pengisian kolom tanpa salinan dari perpustakaan cython.

Akan sangat bagus jika Anda bersedia membagikan kode apa pun yang Anda miliki (bahkan setengah berfungsi) untuk membantu saya memulai.

Apakah berikut ini akan menjadi pendekatan yang masuk akal? Saya melangkah ke samping menciptakan kembali logika pemblokiran dengan bekerja dari kerangka data prototipe.

Tipe d mana yang memerlukan perlakuan khusus selain kategorikal?

Tentu saja, menggunakan kerangka data yang dibuat tidak aman sampai diisi ...

import numpy as np
from pandas.core.index import _ensure_index
from pandas.core.internals import BlockManager
from pandas.core.generic import NDFrame
from pandas.core.frame import DataFrame
from pandas.core.common import CategoricalDtype
from pandas.core.categorical import Categorical
from pandas.core.index import Index

def allocate_like(df, size, keep_categories=False):
    # define axes (waiting for #939 (RangeIndex))
    axes = [df.columns.values.tolist(), Index(np.arange(size))]

    # allocate and create blocks
    blocks = []
    for block in df._data.blocks:
        # special treatment for non-ordinary block types
        if isinstance(block.dtype, CategoricalDtype):
            if keep_categories:
                categories = block.values.categories
            else:
                categories = Index([])
            values = Categorical(values=np.empty(shape=block.values.shape,
                                                 dtype=block.values.codes.dtype),
                                 categories=categories,
                                 fastpath=True)
        # ordinary block types
        else:
            new_shape = (block.values.shape[0], size)
            values = np.empty(shape=new_shape, dtype=block.dtype)

        new_block = block.make_block_same_class(values=values,
                                                placement=block.mgr_locs.as_array)
        blocks.append(new_block)

    # create block manager
    mgr = BlockManager(blocks, axes)

    # create dataframe
    return DataFrame(mgr)


# create a prototype dataframe
import pandas as pd
a = np.empty(0, dtype=('i4,i4,f4,f4,f4,a10'))
df = pd.DataFrame(a)
df['cat_col'] = pd.Series(list('abcabcdeff'), dtype="category")

# allocate an alike dataframe
df1 = allocate_like(df, size=10)

@ARF1 tidak begitu yakin apa tujuan akhirnya
dapatkah Anda memberikan contoh sederhana?

concat lebih lanjut dengan copy=False umumnya akan mengesampingkan langkah ini

@jreback Saya ingin menggunakan pustaka cython untuk membaca data volume besar kolom demi kolom dari penyimpanan data terkompresi yang ingin saya kompres langsung ke dalam kerangka data tanpa perantara menyalin karena alasan kinerja.

Meminjam dari solusi numpy yang biasa dalam kasus seperti itu, saya ingin mengalokasikan memori untuk kerangka data sehingga saya dapat meneruskan pointer ke wilayah memori yang dialokasikan ini ke perpustakaan cython saya yang kemudian dapat menggunakan c-pointer/c-array biasa yang sesuai dengan wilayah memori tersebut untuk mengisi kerangka data secara langsung tanpa langkah penyalinan perantara (atau pembuatan objek python perantara). Opsi untuk mengisi kerangka data dengan beberapa utas cython secara paralel dengan gil yang dirilis akan menjadi keuntungan tambahan.

Dalam kode semu (yang disederhanakan), idom akan menjadi seperti:

df = fn_to_allocate_memory()
colums = df.columns.values
column_indexes = []
for i in xrange(len(df._data.blocks)):
    column_indexes.extend(df._data.blocks[i].mgr_locs.as_array)
block_arrays = [df._data.blocks[i].values for i in len(df._data.blocks)]

some_cython_library.fill_dataframe_with_content(columns, column_indexes, block_arrays)

Apakah ini masuk akal bagi Anda?

Seperti yang saya pahami concat dengan copy=False tidak akan menggabungkan kolom dengan tipe d yang identik menjadi blok tetapi operasi di telepon akan memicu ini - menghasilkan penyalinan yang saya coba hindari. Atau apakah saya salah memahami operasi internal panda?

Sementara saya telah membuat beberapa kemajuan dengan instantiasi kerangka data besar (tidak terisi) (faktor ~ 6,7) saya masih jauh dari kecepatan numpy. Hanya faktor lain dari ~90 lagi...

In [157]: a = np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4,a10'))

In [158]: df = pd.DataFrame(a)

In [162]: %timeit np.empty(int(1e6), dtype=('i8,i4,i4,f4,f4,f4,a10'))
1000 loops, best of 3: 247 µs per loop

In [163]: %timeit allocate_like(df, size=int(1e6))
10 loops, best of 3: 22.4 ms per loop

In [164]: %timeit pd.DataFrame(np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4,a10')))

10 loops, best of 3: 150 ms per loop

Harapan lain adalah bahwa pendekatan ini juga memungkinkan instantiasi berulang yang lebih cepat dari DataFrames berbentuk identik ketika data volume kecil sering dibaca. Itu belum menjadi tujuan utama sejauh ini tetapi secara tidak sengaja saya membuat kemajuan yang lebih baik dengan ini: hanya faktor ~4,8 untuk mencapai kecepatan numpy.

In [157]: a = np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4,a10'))

In [158]: df = pd.DataFrame(a)

In [159]: %timeit np.empty(0, dtype=('i8,i4,i4,f4,f4,f4,a10'))
10000 loops, best of 3: 79.9 µs per loop

In [160]: %timeit allocate_like(df, size=0)
1000 loops, best of 3: 379 µs per loop

In [161]: %timeit pd.DataFrame(np.empty(0, dtype=('i4,i4,f4,f4,f4,a10')))
1000 loops, best of 3: 983 µs per loop

Sunting

Pengaturan waktu di atas melukiskan gambaran yang terlalu pesimis karena membandingkan apel dengan jeruk: sementara kolom string numpy dibuat sebagai string asli yang panjangnya diperbaiki, kolom yang setara dalam panda akan dibuat sebagai array objek python. Membandingkan sama dengan sama mendorong instantiasi DataFrame ke kecepatan numpy dengan pengecualian pembuatan indeks yang bertanggung jawab atas sekitar 92% dari waktu instantiasi.

@ARF1 jika Anda ingin kecepatan numpy, gunakan saja numpy. Saya tidak yakin apa yang sebenarnya Anda lakukan atau apa yang Anda lakukan di cython. Solusi yang biasa adalah memotong perhitungan Anda, meneruskan tipe d tunggal ke cython atau hanya mendapatkan mesin yang lebih besar.

DataFrames melakukan lebih dari sekadar numpy tentang bagaimana mereka menggambarkan dan memanipulasi data. Bukan apa yang sebenarnya Anda lakukan dengan mereka.

hampir semua operasi pandas menyalin. (seperti halnya sebagian besar operasi numpy), jadi tidak yakin apa yang Anda cari.

@jreback Saat ini saya menggunakan numpy tetapi saya memiliki tipe campuran yang hanya dapat (dengan mudah) ditangani dengan array terstruktur. Namun, array terstruktur secara inheren adalah urutan baris-utama yang berbenturan dengan dimensi analisis tipikal saya yang mengarah ke kinerja yang buruk. Panda terlihat seperti alternatif alami karena urutan kolom-utamanya - jika saya bisa memasukkan data ke dalam kerangka data dengan kecepatan yang baik.

Tentu saja alternatifnya akan menggunakan dict dari array numpy yang diketik berbeda tetapi itu membuat analisis menjadi sulit karena mengiris dll tidak mungkin lagi.

Solusi yang biasa adalah memotong perhitungan Anda, meneruskan tipe d tunggal ke cython.

Itulah yang saya lakukan dengan variabel block_arrays dalam contoh saya.

atau hanya mendapatkan mesin yang lebih besar.

Faktor 100+ lebih cepat adalah sedikit tantangan finansial bagi saya. ;-)

@ARF1 Anda memiliki model yang sangat aneh tentang cara kerja sesuatu. Biasanya Anda membuat sejumlah kecil bingkai data, lalu mengerjakannya. Kecepatan pembuatan adalah sebagian kecil dari perhitungan atau manipulasi nyata.

@jreback : ini bukan model yang aneh. Mungkin ini model yang aneh jika Anda melihat sesuatu dari perspektif python murni. Jika Anda bekerja dengan kode C++, cara termudah untuk membaca data ke objek python adalah dengan memberikan pointer ke objek python yang sudah ada sebelumnya. Jika Anda melakukan ini dalam konteks sensitif kinerja, Anda menginginkan cara yang murah dan stabil (dalam arti lokasi memori) untuk membuat objek python.

Sejujurnya saya tidak yakin mengapa sikap ini biasa terjadi di papan panda. Saya pikir sangat disayangkan, sejauh saya mengerti bahwa panda adalah konstruksi tingkat yang lebih tinggi daripada numpy, masih bisa lebih mudah bagi orang untuk mengembangkan panda "di atas". Pandas DataFrame sejauh ini merupakan tipe yang paling diinginkan untuk digunakan jika Anda memiliki kode C yang ingin memasukkan data tabular ke dalam python, jadi ini benar-benar tampak seperti kasus penggunaan yang penting.

Tolong jangan mengambil apa yang saya tulis secara negatif, jika saya tidak berpikir panda DataFrames begitu mengagumkan, saya hanya akan menggunakan catatan numpy atau sesuatu seperti itu dan selesai dengan itu.

@ARF1 : Pada akhirnya, saya tidak ingat alasannya, tetapi yang terbaik yang dapat saya lakukan adalah membuat DataFrame untuk setiap tipe numerik dari array numpy dengan Copy=False, dan kemudian menggunakan pandas.concat dengan Copy=False lagi untuk menggabungkan mereka. Saat Anda membuat satu tipe DataFrame dari array numpy, berhati-hatilah dengan orientasi array numpy. Jika orientasinya salah, maka array numpy yang sesuai dengan setiap kolom tidak akan dibuat-buat, dan panda tidak menyukai ini dan akan membuat salinan pada kesempatan pertama. Anda dapat menempelkan Kategoris di bagian akhir, karena mereka tidak dikonsolidasikan dan tidak boleh memicu salinan apa pun dari sisa bingkai.

Saya merekomendasikan menulis beberapa unit test yang melakukan operasi ini selangkah demi selangkah dan terus-menerus mengambil pointer ke data yang mendasarinya (melalui array_interface dari array numpy yang mendasarinya) dan memverifikasi bahwa mereka sama untuk memastikan bahwa salinannya benar-benar dihilangkan. Ini adalah keputusan yang sangat disayangkan oleh panda yang menyalin/menempatkan parameter TIDAK harus dihormati. Artinya, bahkan jika Anda menyetel mis. copy=False untuk konstruktor DataFrame, panda akan tetap melakukan penyalinan jika diperlukan untuk membuat DataFrame. Fakta bahwa panda melakukan ini alih-alih melempar ketika argumen tidak dapat dihormati membuat penulisan kode yang andal yang menghindari salinan sangat melelahkan, dan membutuhkan sangat metodis. Jika Anda tidak menulis tes unit untuk memverifikasi, Anda mungkin secara tidak sengaja mengubah sesuatu nanti yang menyebabkan salinan dibuat, dan itu akan terjadi secara diam-diam dan merusak kinerja Anda.

@quicknir jika Anda mengatakannya. Saya pikir Anda hanya perlu membuat profil sebelum mencoba mengoptimalkan berbagai hal. Seperti yang saya katakan sebelumnya, dan prob akan melakukannya lagi. Waktu konstruksi tidak boleh mendominasi apa pun. Jika ya, maka Anda hanya menggunakan DataFrame untuk menyimpan sesuatu, jadi apa gunanya menggunakannya? Jika tidak mendominasi, lalu apa masalahnya?

@jreback Anda menulis itu, dengan asumsi bahwa saya belum membuat profil. Sebenarnya, saya punya. Kami memiliki kode c++ dan python yang melakukan de-serialisasi data tabular dari format data yang sama. Sementara saya mengharapkan kode python memiliki sedikit overhead, saya pikir perbedaannya harus kecil, karena waktu baca disk harus mendominasi. Ini tidak terjadi, sebelum saya pergi dan dengan sangat hati-hati mengerjakan ulang hal-hal untuk meminimalkan salinan, versi python memakan waktu dua kali lebih lama atau lebih buruk dibandingkan dengan kode C++, dan hampir semua overhead hanya dalam membuat DataFrame. Dengan kata lain, butuh waktu lama untuk membuat DataFrame dengan ukuran tertentu yang sangat besar yang isinya tidak saya pedulikan sama sekali, seperti membaca, mendekompresi, dan menulis data yang saya pedulikan ke dalam DataFrame itu. Itu kinerja yang sangat buruk.

Jika saya adalah pengguna akhir kode ini dengan operasi tertentu dalam pikiran, mungkin apa yang Anda katakan tentang konstruksi tidak mendominasi akan valid. Pada kenyataannya, saya seorang pengembang, dan pengguna akhir kode ini adalah orang lain. Saya tidak tahu persis apa yang akan mereka lakukan dengan DataFrame, DataFrame adalah satu-satunya cara untuk mendapatkan representasi dalam memori dari data pada disk. Jika mereka ingin melakukan sesuatu yang sangat sederhana dengan data di disk, mereka masih harus melalui format DataFrame.

Jelas, saya dapat mendukung lebih banyak cara untuk mendapatkan data (misalnya konstruksi numpy), tetapi ini akan sangat meningkatkan percabangan dalam kode, dan membuat segalanya lebih sulit bagi saya sebagai pengembang. Jika ada beberapa alasan mendasar mengapa DataFrames harus sangat lambat, saya akan mengerti, dan memutuskan apakah akan mendukung DataFrame, numpy, atau keduanya. Tetapi tidak ada alasan nyata mengapa itu harus begitu lambat. Seseorang dapat menulis metode DataFrame.empty yang mengambil larik tupel di mana setiap tupel berisi nama dan jenis kolom, dan jumlah baris.

Inilah perbedaan yang saya maksud antara pengguna pendukung dan penulis perpustakaan. Lebih mudah untuk menulis kode Anda sendiri daripada menulis perpustakaan. Dan lebih mudah untuk membuat perpustakaan Anda hanya mendukung pengguna daripada penulis perpustakaan lainnya. Saya hanya berpikir dalam kasus ini, alokasi kosong DataFrames akan menjadi buah gantung rendah di panda yang akan membuat hidup orang-orang seperti saya dan @ARF1 lebih mudah.

baik jika Anda ingin memiliki sol didokumentasikan diuji wajar, semua telinga. pandas memiliki beberapa pengguna/pengembang. Itulah alasan DataFrame sangat serbaguna dan alasan yang sama mengapa perlu banyak pemeriksaan kesalahan dan inferensi. Anda dipersilakan untuk melihat apa yang dapat Anda lakukan seperti yang dijelaskan di atas.

Saya bersedia meluangkan waktu untuk menerapkan ini, tetapi hanya jika ada konsensus yang masuk akal tentang desain dari beberapa pengembang panda. Jika saya mengajukan permintaan tarik dan ada hal-hal tertentu yang ingin diubah orang, itu keren. Atau jika saya menyadari setelah saya menghabiskan sepuluh jam ke dalamnya bahwa tidak ada cara untuk melakukan sesuatu dengan bersih, dan satu-satunya cara untuk melakukannya mungkin melibatkan sesuatu yang menurut orang tidak pantas, itu juga keren. Tapi saya tidak terlalu keren dengan menghabiskan X jam dan diberi tahu ini tidak terlalu berguna, implementasinya berantakan, kami tidak berpikir itu benar-benar dapat dibersihkan, memperumit basis kode, dll. Saya tidak tahu apakah Saya jauh dengan sentimen ini, saya belum pernah membuat kontribusi besar untuk proyek OSS sebelumnya jadi saya tidak tahu cara kerjanya. Hanya saja dalam posting awal saya, saya mulai mengusulkan hal ini, dan terus terang saya mendapat kesan dari Anda bahwa itu semacam "di luar cakupan" untuk panda.

Jika Anda mau, saya dapat membuka edisi baru, membuat proposal desain sespesifik mungkin, dan setelah ada umpan balik/persetujuan tentatif, saya akan mengerjakannya ketika saya bisa.

@quicknir kuncinya adalah harus lulus seluruh test suite, yang cukup komprehensif.

Ini bukan di luar cakupan panda, tetapi API harus agak ramah pengguna.

Saya tidak yakin mengapa Anda tidak suka

concat(list_of_arrays,axis=1,copy=False) Saya yakin ini melakukan persis seperti yang Anda inginkan (dan jika tidak, maka tidak jelas apa yang sebenarnya Anda inginkan).

Saya akhirnya menggunakan teknik serupa, tetapi dengan daftar DataFrames yang dibuat dari array numpy tunggal, masing-masing dari jenis yang berbeda.

Pertama, saya pikir saya masih menemukan beberapa salinan ketika saya melakukan teknik ini. Seperti yang saya katakan, panda tidak selalu menghormati copy=False, jadi sangat melelahkan untuk melihat apakah kode Anda benar-benar disalin atau tidak. Saya sangat berharap bahwa untuk panda 17, pengembang akan mempertimbangkan untuk menjadikan copy=True sebagai default, dan kemudian copy=False throws ketika salinan tidak dapat dihapus. Tapi bagaimanapun.

Kedua, masalah lain adalah harus menyusun ulang kolom sesudahnya. Ini sangat aneh, satu-satunya cara yang dapat saya temukan untuk melakukan ini tanpa membuat salinan adalah dengan awalnya membuat nama kolom menjadi bilangan bulat yang diurutkan dalam urutan akhir yang diinginkan. Saya kemudian melakukan pengurutan indeks di tempat. Saya kemudian mengubah nama kolom.

Ketiga, saya menemukan bahwa salinan tidak dapat dihindari untuk jenis cap waktu (numpy datetime64).

Saya menulis kode ini beberapa waktu yang lalu sehingga tidak segar dalam pikiran saya. Mungkin saja saya membuat kesalahan, tetapi saya melakukannya dengan cukup hati-hati dan itulah hasil yang saya dapatkan saat itu.

Kode yang Anda berikan di atas bahkan tidak berfungsi untuk array numpy. Gagal dengan: TypeError: tidak dapat menggabungkan objek non-NDFrame. Anda harus membuatnya menjadi DataFrames terlebih dahulu.

Bukannya saya tidak menyukai solusi yang Anda berikan di sini, atau di atas. Saya hanya belum melihat yang sederhana yang berfungsi.

@quicknir baik contoh saya di atas berfungsi. tolong berikan apa yang Anda lakukan dan saya dapat mencoba membantu Anda.

pd.concat([np.zeros((2,2))], axis=1, copy=False)

Saya menggunakan pandas 0.15.2, jadi mungkin ini mulai bekerja di 0.16?

tolong baca doc-string dari pd.concat . Anda harus melewati DataFrame

btw copy=True ADALAH default

Benar, itulah yang saya tulis. Cuplikan kode yang Anda tulis di atas memiliki list_of_arrays, bukan list_of_dataframes. Bagaimanapun, saya pikir kami saling memahami. Saya akhirnya menggunakan metode pd.concat , tapi itu cukup non-sepele, ada banyak gotcha untuk membuat orang tersandung:

1) Anda harus membuat daftar DataFrames. Setiap DataFrame harus memiliki tepat satu dtype yang berbeda. Jadi, Anda harus mengumpulkan semua tipe d yang berbeda sebelum Anda mulai.

2) Setiap DataFrame harus dibuat dari satu larik numpy dengan tipe d yang diinginkan, jumlah baris yang sama, jumlah kolom yang diinginkan, dan flag order ='F'; jika order='C' (default) maka panda akan sering membuat salinan ketika sebaliknya tidak.

3) Abaikan 1) untuk Kategori, mereka tidak digabungkan menjadi satu blok sehingga Anda dapat memasangnya nanti.

4) Saat Anda membuat semua DataFrames individu, kolom harus diberi nama menggunakan bilangan bulat yang mewakili urutan yang Anda inginkan. Jika tidak, mungkin tidak ada cara untuk mengubah urutan kolom tanpa memicu salinan.

5) Setelah membuat daftar DataFrames Anda, gunakan concat. Anda harus dengan susah payah memverifikasi bahwa Anda tidak mengacaukan apa pun, karena copy=False tidak akan muncul jika salinan tidak dapat dihapus, melainkan menyalin secara diam-diam.

6) Urutkan indeks kolom untuk mencapai urutan yang Anda inginkan, lalu ganti nama kolom.

Saya menerapkan prosedur ini dengan ketat. Ini bukan satu liner, ada banyak tempat untuk membuat kesalahan, saya cukup yakin itu masih tidak berfungsi untuk cap waktu, dan ada banyak overhead yang tidak perlu yang dapat dihilangkan dengan tidak menggunakan antarmuka saja. Jika Anda suka, saya dapat menulis draf tentang seperti apa fungsi ini hanya dengan menggunakan API publik, mungkin dikombinasikan dengan beberapa tes untuk melihat apakah itu benar-benar menghilangkan salinan, dan untuk tipe d yang mana.

Juga, copy=False adalah default untuk misalnya konstruktor DataFrame. Poin utama saya adalah lebih bahwa fungsi yang tidak dapat menghormati argumennya harus dilemparkan daripada "melakukan sesuatu yang masuk akal". Artinya, jika copy=False tidak dapat dihormati, pengecualian harus dilemparkan sehingga pengguna tahu bahwa mereka harus mengubah input lain agar penghapusan salinan dapat dilakukan, atau mereka harus mengubah salinan ke True. Salinan tidak boleh terjadi secara diam-diam ketika copy=False, ini lebih mengejutkan dan kurang kondusif bagi pengguna yang sadar akan kinerja untuk menemukan bug.

Anda memiliki banyak langkah di sini yang tidak perlu
tolong tunjukkan contoh aktual seperti yang saya lakukan di atas

Anda memahami bahwa tampilan numpy dapat mengembalikan salinan dengan operasi pembentukan ulang yang sangat sederhana (terkadang) dan bukan yang lain

hanya akan ada jaminan lunak pada salinan karena biasanya tidak mungkin tanpa banyak introspeksi untuk menjamin ini yang menurut definisi mengalahkan tujuan kode kinerja sederhana yang kuat

Perilaku copy=False dalam konstruksi DataFrame konsisten dengan fungsi np.array numpy (misalnya, jika Anda menyediakan daftar array, data akhir akan selalu membuat salinan).

Tampaknya ini adalah celah fitur yang tidak menguntungkan di panda. IMHO, kami tidak akan pernah memiliki solusi yang memuaskan dengan model saat ini untuk panda internal (yang mengkonsolidasikan blok). Sayangnya, itu bukan opsi untuk panda, karena panda masih perlu bekerja untuk orang yang membuat DataFrames dengan banyak kolom.

Yang kita butuhkan adalah implementasi DataFrame alternatif yang dirancang khusus untuk bekerja dengan data rapi yang menyimpan setiap kolom secara independen sebagai array numpy 1D. Ini sebenarnya agak mirip dengan model data di xray , kecuali kami mengizinkan kolom menjadi array N-dimensi.

Saya percaya bahwa konstruktor kerangka data pandas berkinerja tinggi umum yang hanya mengalokasikan ruang adalah non-sepele mengingat berbagai jenis kolom berbeda yang perlu didukung.

Karena itu, tampaknya cukup mudah bagi penulis perpustakaan yang ingin menggunakan kerangka data pandas sebagai wadah data berkinerja tinggi untuk mengimplementasikan konstruktor kerangka data khusus alokasi yang terbatas pada jenis kolom yang mereka butuhkan.

Segmen kode berikut dapat menjadi inspirasi. Ini memungkinkan instantiasi hanya alokasi, kerangka data yang tidak terisi dengan kecepatan mendekati numpy. Perhatikan kode membutuhkan PR #9977:

import numpy as np
from pandas.core.index import _ensure_index
from pandas.core.internals import BlockManager
from pandas.core.generic import NDFrame
from pandas.core.frame import DataFrame
from pandas.core.common import CategoricalDtype
from pandas.core.categorical import Categorical
from pandas.core.index import RangeIndex

def allocate_like(df, size, keep_categories=False):
    # define axes (uses PR #9977)
    axes = [df.columns.values.tolist(), RangeIndex(size)]

    # allocate and create blocks
    blocks = []
    for block in df._data.blocks:
        # special treatment for non-ordinary block types
        if isinstance(block.dtype, CategoricalDtype):
            if keep_categories:
                categories = block.values.categories
            else:
                categories = Index([])
            values = Categorical(values=np.empty(shape=block.values.shape,
                                                 dtype=block.values.codes.dtype),
                                 categories=categories,
                                 fastpath=True)
        # ordinary block types
        else:
            new_shape = (block.values.shape[0], size)
            values = np.empty(shape=new_shape, dtype=block.dtype)

        new_block = block.make_block_same_class(values=values,
                                                placement=block.mgr_locs.as_array)
        blocks.append(new_block)

    # create block manager
    mgr = BlockManager(blocks, axes)

    # create dataframe
    return DataFrame(mgr)

Dengan contoh konstruktor allocate_like() hukuman kinerja cf numpy hanya x2.3 (biasanya x333) untuk array besar dan x3.3 (biasanya x8.9) untuk array berukuran nol:

In [2]: import numpy as np

In [3]: import pandas as pd

In [4]: a = np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4'))

# create template-dataframe
In [5]: df = pd.DataFrame(a)

# large dataframe timings
In [6]: %timeit np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4'))
1000 loops, best of 3: 212 µs per loop

In [7]: %timeit allocate_like(df, size=int(1e6))
1000 loops, best of 3: 496 µs per loop

In [8]: %timeit pd.DataFrame(np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4')))
10 loops, best of 3: 70.6 ms per loop

# zero-size dataframe timing
In [9]: %timeit np.empty(0, dtype=('i4,i4,f4,f4,f4'))
10000 loops, best of 3: 108 µs per loop

In [10]: %timeit allocate_like(df, size=0)
1000 loops, best of 3: 360 µs per loop

In [11]: %timeit pd.DataFrame(np.empty(0, dtype=('i4,i4,f4,f4,f4')))
1000 loops, best of 3: 959 µs per loop

Maaf, saya kehilangan jejak ini untuk sementara waktu. @ARF1 , terima kasih banyak untuk contoh kode di atas. Sangat bagus, bersama dengan metrik kinerja.

Saya benar-benar merasa bahwa membuat kelas yang sesuai dengan tata letak DataFrame, tanpa data apa pun, akan membuat kode seperti di atas jauh lebih alami, dan juga mungkin lebih berkinerja. Kelas ini juga dapat digunakan kembali misalnya saat melakukan pengindeksan ulang baris.

Apa yang pada dasarnya saya usulkan adalah sesuatu seperti ini: kelas yang disebut DataFrameLayout, yang membungkus dtypes, nama kolom, dan pemesanan kolom. Misalnya, itu bisa menyimpan dict dari dtype ke nomor kolom (untuk memesan), dan array terpisah dengan semua nama. Dari tata letak ini, Anda dapat melihat bahwa iterasi sederhana dan elegan di atas dict akan memungkinkan pembuatan pengelola blok dengan cepat. Kelas ini kemudian dapat digunakan di tempat-tempat seperti konstruktor kosong, atau dalam operasi pengindeksan ulang.

Saya pikir abstraksi seperti itu diperlukan untuk data yang lebih kompleks. Dalam beberapa hal, DataFrame adalah tipe data komposit, dan DataFrameLayout akan menentukan sifat komposisi yang tepat.

Omong-omong, saya pikir sesuatu yang serupa diperlukan untuk Kategorikal; yaitu perlu ada abstraksi CategoricalType yang menyimpan kategori, apakah mereka diurutkan atau tidak, tipe array pendukung, dll. Artinya, semuanya kecuali data aktual. Faktanya, ketika Anda memikirkan DataFrameLayout, Anda menyadari bahwa semua kolom harus memiliki tipe yang ditentukan sepenuhnya, dan itu saat ini bermasalah untuk Categoricals.

Apa pendapat orang tentang dua kelas ini?

@quicknir Kami sudah memiliki kelas CategoricalDtype -- Saya setuju bahwa itu dapat diperluas ke CategoricalType Anda jelaskan.

Saya tidak sepenuhnya yakin tentang kelas DataFrameLayout . Pada dasarnya, saya pikir kita bisa menggunakan model data alternatif yang lebih sederhana untuk bingkai data (lebih mirip dengan cara melakukannya di R atau Julia). Ada beberapa minat dalam hal semacam ini dan saya menduga itu akan terjadi pada akhirnya dalam beberapa bentuk, tetapi mungkin tidak dalam waktu dekat (dan mungkin tidak pernah sebagai bagian dari proyek panda).

@quicknir yeh, DataFrameLayout menciptakan kembali roda di sini. Kami sudah memiliki spesifikasi dtype, mis

In [14]: tm.makeMixedDataFrame().to_records().dtype
Out[14]: dtype([('index', '<i8'), ('A', '<f8'), ('B', '<f8'), ('C', 'O'), ('D', '<M8[ns]')])

@jreback Ini bukan menciptakan kembali roda, karena spesifikasi dtype memiliki beberapa masalah utama:

1) Sejauh yang saya bisa lihat, to_records() akan melakukan salinan mendalam dari seluruh DataFrame. Mendapatkan spec (saya hanya akan menggunakan istilah ini mulai sekarang) untuk DataFrame harus murah dan mudah.

2) Output dari to_records adalah tipe numpy. Salah satu implikasi dari ini adalah bahwa saya tidak melihat bagaimana ini dapat diperluas untuk mendukung Kategoris dengan benar.

3) Metode penyimpanan spesifikasi internal ini tidak mudah kompatibel dengan cara data disimpan di dalam DataFrame (yaitu dalam blok seperti dtype). Membuat blok dari spesifikasi semacam itu melibatkan banyak pekerjaan ekstra yang dapat dihilangkan dengan menyimpan spesifikasi dengan cara seperti yang saya sarankan, dengan dict dari dtype ke nomor kolom. Ketika Anda memiliki DataFrame dengan 2000 kolom, ini akan menjadi mahal.

Singkatnya, dtype dari representasi catatan lebih merupakan solusi karena kurangnya spesifikasi yang tepat. Ini tidak memiliki beberapa fitur utama dan kinerjanya jauh lebih buruk.

Ada banyak utas di SO yang meminta fitur ini.

Sepertinya saya bahwa semua masalah ini berasal dari BlockManager yang mengkonsolidasikan kolom terpisah menjadi satu potongan memori ('blok').
Bukankah perbaikan termudah adalah tidak mengkonsolidasikan data ke dalam blok ketika copy=False ditentukan.

Saya memiliki BlockManager yang tidak ditambal monyet:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
bahwa saya digunakan untuk mengatasi masalah ini.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat