ป้องกันการบีบบังคับเฟรมข้อมูลแพนด้าขณะทำดัชนีและแทรกแถว


16

ฉันทำงานกับเฟรมข้อมูลแพนด้าแต่ละแถว แต่ฉันสะดุดปัญหาการบีบบังคับในขณะที่จัดทำดัชนีและแทรกแถว ดูเหมือนว่า Pandas ต้องการที่จะบีบบังคับจาก int / float แบบผสมให้กับทุกประเภททุ่นและฉันไม่เห็นการควบคุมที่ชัดเจนเกี่ยวกับพฤติกรรมนี้

ยกตัวอย่างเช่นที่นี่เป็นกรอบข้อมูลที่เรียบง่ายด้วยaเป็นintและbเป็น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

นี่คือปัญหาการบีบบังคับขณะสร้างดัชนีหนึ่งแถว:

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}

และนี่คือปัญหาการบีบบังคับในขณะที่แทรกแถวหนึ่ง:

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

ในทั้งสองกรณีฉันต้องการให้aคอลัมน์ยังคงเป็นประเภทจำนวนเต็มแทนที่จะถูกบังคับให้เป็นประเภทลอย


ฉันพบสิ่งนี้แต่ฉันไม่พบว่าปัญหาได้รับการแก้ไขอย่างมีประสิทธิภาพหรือไม่ ในเวลานั้นฉันคิดว่าคุณน่าจะทำได้:df.loc[[0], df.columns]
Dani Mesejo


เสียงเหมือน pd.DataFrame ไม่รองรับการมิกซ์เสียงในอินสแตนซ์ pandas.pydata.org/pandas-docs/stable/reference/api/… dtype param รองรับประเภทเดียวเท่านั้น .read_[type]สนับสนุน dtypes หลาย ๆ ที่ ...
Quentin

คำตอบ:


4

หลังจากขุดบางส่วนนี่เป็นวิธีแก้ปัญหาที่น่าเกลียดอย่างมาก (คำตอบที่ดีจะได้รับการยอมรับ)

การเล่นโวหารที่พบที่นี่คือคอลัมน์ที่ไม่ใช่ตัวเลขหยุดการบีบบังคับดังนั้นนี่คือวิธีสร้างดัชนีหนึ่งแถวไปยัง a dict:

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

และการแทรกแถวสามารถทำได้โดยการสร้างกรอบข้อมูลใหม่ด้วยหนึ่งแถว:

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

เทคนิคทั้งสองนี้ไม่เหมาะสำหรับเฟรมข้อมูลขนาดใหญ่ดังนั้นฉันจะขอขอบคุณคำตอบที่ดีกว่า!


คุณสามารถบีบบังคับโพสต์ต่อท้ายได้เสมอdf['a'] = df.a.astype(mytype)... มันยังสกปรกอยู่ดีและอาจไม่มีประสิทธิภาพ
เควนติน

.astype()เป็นอันตรายต่อการลอย -> จำนวนเต็ม; มันไม่มีปัญหาในการเปลี่ยน1.1ไป1ดังนั้นคุณจึงจำเป็นจริงๆที่จะแน่ใจว่าทั้งหมดของค่าของคุณ 'จำนวนเต็มเช่น' ก่อนที่จะทำมัน น่าจะดีที่สุดที่จะใช้pd.to_numericกับdowncast='integer'
ALollz

2

รากของปัญหาคือ

  1. การสร้างดัชนีของ pandas dataframe ส่งคืนชุดของ pandas

เราจะเห็นว่า:

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

และซีรี่ย์สามารถมีได้เพียงหนึ่งชนิดในกรณีของคุณทั้ง int64 หรือ float64

มีวิธีแก้ปัญหาสองข้อมาที่หัวของฉัน:

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}

หรือ

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. เมื่อคุณต่อท้ายพจนานุกรมกับชื่อไฟล์มันจะแปลงพจนานุกรมเป็นซีรีส์ก่อนแล้วจึงต่อท้าย (ดังนั้นปัญหาเดียวกันจะเกิดขึ้นอีกครั้ง)

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

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

ดังนั้นภาพรวมของคุณจึงเป็นภาพที่มั่นคงหรืออย่างอื่นเราสามารถ:

df.append(pd.Series({'a': 5, 'b': 4.4}, dtype=object, name=1))
#    a    b
# 0  1  2.2
# 1  5  4.4

ความคิดที่ดีที่จะใช้objectชนิดข้อมูล! อีกอันหนึ่งคือการสร้างวัตถุ DataFrame ตั้งแต่ต้น:df = pd.DataFrame({'a': [1], 'b': [2.2]}, dtype=object)
Mike T

2

เมื่อใดก็ตามที่คุณได้รับข้อมูลจาก dataframe หรือผนวกข้อมูลไปยัง dataframe และจำเป็นต้องรักษาชนิดข้อมูลเดียวกันให้หลีกเลี่ยงการแปลงเป็นโครงสร้างภายในอื่น ๆ ที่ไม่ทราบชนิดข้อมูลที่จำเป็น

เมื่อคุณทำdf.loc[0]มันจะแปลงไปpd.Series,

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

และตอนนี้จะมีเพียงหนึ่งเดียวSeries dtypeดังนั้นบีบบังคับไปintfloat

แทนที่จะให้โครงสร้างpd.DataFrame,

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

เลือกแถวที่ต้องการเป็นเฟรมแล้วแปลงเป็น dict

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

ในการเพิ่มแถวใหม่ให้ใช้pd.DataFrame.appendฟังก์ชั่นนุ่น

>>> 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

ข้างต้นจะไม่ทำให้เกิดการแปลงประเภท

>>> df.dtypes
a      int64
b    float64
dtype: object

ว้าวต้องอ่านรหัสที่สองบล็อกสามครั้งเพื่อรับมัน นั่นช่างลึกซึ้งมาก นี่ดีกว่าสิ่งที่ฉันทำในอดีต ... วนผ่านดาต้าเฟรมสุดท้ายและกำหนดค่าใหม่ด้วยประเภทข้อมูลที่ถูกต้อง
VanBantam

1
โอ้ ดีใจที่ได้ช่วย😊 @VanBantam
Vishnudev

1

แนวทางที่แตกต่างกับการปรับเปลี่ยนข้อมูลเล็กน้อย:

สมมติว่าคุณมีรายการพจนานุกรม (หรือ dataframes)

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

โดยที่แต่ละพจนานุกรมหมายถึงแถว (หมายเหตุรายการในพจนานุกรมที่สอง) จากนั้นคุณสามารถสร้างชื่อไฟล์ได้อย่างง่ายดายผ่าน:

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

และคุณคงประเภทของคอลัมน์ไว้ ดูconcat

ดังนั้นถ้าคุณมีไฟล์ข้อมูลและรายการ dicts คุณก็สามารถใช้ได้

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

0

ในกรณีแรกที่คุณสามารถทำงานร่วมกับnullable จำนวนเต็มชนิดข้อมูล การเลือกซีรีส์ไม่ได้ถูกบีบบังคับfloatและค่าจะถูกวางไว้ในobjectคอนเทนเนอร์ 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

ด้วยไวยากรณ์ของคุณมันเกือบจะใช้ได้กับกรณีที่สองด้วยเช่นobjectกัน

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

อย่างไรก็ตามเราสามารถทำการเปลี่ยนแปลงเล็กน้อยสำหรับไวยากรณ์สำหรับการเพิ่มแถวที่ท้าย (ด้วย RangeIndex) และตอนนี้ประเภทจะได้รับการจัดการอย่างถูกต้อง

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.