Cegah pemaksaan bingkai data panda saat mengindeks dan menyisipkan baris

16

Saya bekerja dengan baris individual bingkai data panda, tapi saya tersandung masalah paksaan saat mengindeks dan menyisipkan baris. Panda sepertinya selalu ingin memaksa dari int campuran / float ke semua tipe float, dan saya tidak bisa melihat kontrol yang jelas pada perilaku ini.

Sebagai contoh, ini adalah kerangka data sederhana dengan aas intdan bas float:

import pandas as pd
pd.__version__  # '0.25.2'

df = pd.DataFrame({'a': [1], 'b': [2.2]})
print(df)
#    a    b
# 0  1  2.2
print(df.dtypes)
# a      int64
# b    float64
# dtype: object

Berikut ini adalah masalah pemaksaan saat mengindeks satu baris:

print(df.loc[0])
# a    1.0
# b    2.2
# Name: 0, dtype: float64
print(dict(df.loc[0]))
# {'a': 1.0, 'b': 2.2}

Dan ini adalah masalah pemaksaan saat memasukkan satu baris:

df.loc[1] = {'a': 5, 'b': 4.4}
print(df)
#      a    b
# 0  1.0  2.2
# 1  5.0  4.4
print(df.dtypes)
# a    float64
# b    float64
# dtype: object

Dalam kedua contoh, saya ingin akolom tetap sebagai tipe integer, daripada dipaksa ke tipe float.

Mike T
sumber
Saya menemukan ini , tetapi saya tidak dapat menemukan jika masalah ini secara efektif diselesaikan. Sementara itu saya kira Anda bisa melakukan:df.loc[[0], df.columns]
Dani Mesejo
Kedengarannya seperti pd.DataFrame tidak mendukung pencampuran tipe pada instantiation? pandas.pydata.org/pandas-docs/stable/reference/api/… param dtype hanya mendukung satu tipe. .read_[type]mendukung beberapa dtypes ...
Quentin

Jawaban:

4

Setelah beberapa penggalian, berikut adalah beberapa solusi yang sangat jelek. (Jawaban yang lebih baik akan diterima.)

Sebuah kekhasan yang ditemukan di sini adalah bahwa kolom non-numerik menghentikan paksaan, jadi inilah cara mengindeks satu baris ke dict:

dict(df.assign(_='').loc[0].drop('_', axis=0))
# {'a': 1, 'b': 2.2}

Dan memasukkan baris dapat dilakukan dengan membuat bingkai data baru dengan satu baris:

df = df.append(pd.DataFrame({'a': 5, 'b': 4.4}, index=[1]))
print(df)
#    a    b
# 0  1  2.2
# 1  5  4.4

Kedua trik ini tidak dioptimalkan untuk bingkai data yang besar, jadi saya akan sangat menghargai jawaban yang lebih baik!

Mike T
sumber
Anda selalu bisa hanya memaksa menambahkan postingan df['a'] = df.a.astype(mytype)... Ini masih kotor dan mungkin tidak efisien.
Quentin
.astype()berbahaya untuk float -> integer; tidak ada masalah berubah 1.1menjadi 1, jadi Anda benar-benar harus memastikan semua nilai Anda 'seperti integer' sebelum melakukannya. Mungkin yang terbaik untuk digunakan pd.to_numericdengandowncast='integer'
ALollz
2

Akar masalahnya adalah itu

  1. Pengindeksan panda dataframe mengembalikan seri panda

Kita dapat melihat bahwa:

type(df.loc[0])
# pandas.core.series.Series

Dan seri hanya dapat memiliki satu jenis, dalam kasus Anda baik int64 atau float64.

Ada dua solusi yang muncul di kepala saya:

print(df.loc[[0]])
# this will return a dataframe instead of series
# so the result will be
#    a    b
# 0  1  2.2

# but the dictionary is hard to read
print(dict(df.loc[[0]]))
# {'a': 0    1
# Name: a, dtype: int64, 'b': 0    2.2
# Name: b, dtype: float64}

atau

print(df.astype(object).loc[0])
# this will change the type of value to object first and then print
# so the result will be
# a      1
# b    2.2
# Name: 0, dtype: object

print(dict(df.astype(object).loc[0]))
# in this way the dictionary is as expected
# {'a': 1, 'b': 2.2}
  1. Saat Anda menambahkan kamus ke bingkai data, kamus akan mengonversi kamus menjadi Seri terlebih dahulu lalu menambahkan. (Jadi masalah yang sama terjadi lagi)

https://github.com/pandas-dev/pandas/blob/master/pandas/core/frame.py#L6973

if isinstance(other, dict):
    other = Series(other)

Jadi langkah Anda sebenarnya adalah langkah yang solid, atau kita bisa:

df.append(pd.Series({'a': 5, 'b': 4.4}, dtype=object, name=1))
#    a    b
# 0  1  2.2
# 1  5  4.4
Hongpei
sumber
Ide bagus untuk menggunakan objecttipe data! Satu lagi adalah membuat objek DataFrame dari awal:df = pd.DataFrame({'a': [1], 'b': [2.2]}, dtype=object)
Mike T
2

Setiap kali Anda mendapatkan data dari kerangka data atau menambahkan data ke kerangka data dan perlu menjaga tipe data tetap sama, hindari konversi ke struktur internal lain yang tidak mengetahui tipe data yang dibutuhkan.

Ketika Anda melakukan df.loc[0]itu mengkonversi ke pd.Series,

>>> type(df.loc[0])
<class 'pandas.core.series.Series'>

Dan sekarang, Serieshanya akan memiliki satu dtype. Dengan demikian memaksa intuntuk float.

Alih-alih menjaga struktur sebagai pd.DataFrame,

>>> type(df.loc[[0]])
<class 'pandas.core.frame.DataFrame'>

Pilih baris yang diperlukan sebagai bingkai dan kemudian konversikan ke dict

>>> df.loc[[0]].to_dict(orient='records')
[{'a': 1, 'b': 2.2}]

Demikian pula, untuk menambahkan baris baru, Gunakan pd.DataFrame.appendfungsi panda ,

>>> df = df.append([{'a': 5, 'b': 4.4}]) # NOTE: To append as a row, use []
   a    b
0  1  2.2
0  5  4.4

Di atas tidak akan menyebabkan konversi jenis,

>>> df.dtypes
a      int64
b    float64
dtype: object
Vishnudev
sumber
Wow harus membaca bahwa blok kode kedua tiga kali untuk mendapatkannya. Itu sangat halus. Ini jauh lebih baik daripada yang telah saya lakukan di masa lalu ... loop melalui kerangka data akhir dan menetapkan kembali nilai-nilai dengan tipe data yang benar (ya apa yang saya lakukan adalah solusi mengerikan yang benar-benar tidak akan skala.).
VanBantam
1
Oh Senang itu membantu 😊 @VanBantam
Vishnudev
1

Pendekatan berbeda dengan sedikit manipulasi data:

Asumsikan Anda memiliki daftar kamus (atau bingkai data)

lod=[{'a': [1], 'b': [2.2]}, {'a': [5], 'b': [4.4]}]

di mana setiap kamus mewakili satu baris (perhatikan daftar dalam kamus kedua). Kemudian Anda dapat membuat kerangka data dengan mudah melalui:

pd.concat([pd.DataFrame(dct) for dct in lod])
   a    b
0  1  2.2
0  5  4.4

dan Anda mempertahankan jenis kolom. Lihat konser

Jadi jika Anda memiliki kerangka data dan daftar dicts, Anda bisa menggunakannya

pd.concat([df] + [pd.DataFrame(dct) for dct in lod])
Quickbeam2k1
sumber
0

Dalam kasus pertama, Anda bisa bekerja dengan tipe data integer nullable . Pilihan Seri tidak memaksa floatdan nilai ditempatkan dalam objectwadah. Kamus kemudian dibuat dengan benar, dengan nilai dasar disimpan sebagai a np.int64.

df = pd.DataFrame({'a': [1], 'b': [2.2]})
df['a'] = df['a'].astype('Int64')

d = dict(df.loc[0])
#{'a': 1, 'b': 2.2}

type(d['a'])
#numpy.int64

Dengan sintaks Anda, ini hampir berfungsi untuk kasus kedua juga, tetapi ini tidak benar object, jadi tidak bagus:

df.loc[1] = {'a': 5, 'b': 4.4}
#   a    b
#0  1  2.2
#1  5  4.4

df.dtypes
#a     object
#b    float64
#dtype: object

Namun, kita dapat membuat perubahan kecil pada sintaks untuk menambahkan baris di akhir (dengan RangeIndex) dan sekarang jenis ditangani dengan benar.

df = pd.DataFrame({'a': [1], 'b': [2.2]})
df['a'] = df['a'].astype('Int64')

df.loc[df.shape[0], :] = [5, 4.4]
#   a    b
#0  1  2.2
#1  5  4.4

df.dtypes
#a      Int64
#b    float64
#dtype: object
ALollz
sumber