Запобігання примусу кадрів даних панд під час індексації та вставки рядків


16

Я працюю з окремими рядками кадрів даних панди, але я стикаюся з питаннями примусу під час індексації та вставки рядків. Панди, схоже, завжди хочуть переходити від змішаного типу int / float до all-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стовпець залишався цілим типом, а не був примусовим до типу float.


Я знайшов це , але не зміг знайти, якщо ефективно було вирішено проблему. Тим часом я здогадуюсь, що ти міг би зробити:df.loc[[0], df.columns]
Дані


Звучить як pd.DataFrame не підтримує змішування типів при створенні інстанцій? pandas.pydata.org/pandas-docs/stable/reference/api/… параметр dtype підтримує лише один тип. .read_[type]підтримує декілька типів, хоча ...
Квентін

Відповіді:


4

Після деякого копання, ось кілька жахливо потворних обхідних шляхів. (Краща відповідь буде прийнята.)

Тут знайдено химерність , що нечислові стовпці зупиняють примус, тому ось як індексувати один рядок на 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()небезпечний для float -> integer; це не має жодних проблем, 1.1щоб змінитись 1, тому вам потрібно бути впевненим, що всі ваші значення "схожі на цілі", перш ніж робити це. Мабуть, найкраще використовувати pd.to_numericзdowncast='integer'
ALollz

2

Корінь проблеми полягає в тому

  1. Індексація фрейму даних панд повертає ряд панд

Ми можемо побачити це:

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. Коли ви додаєте словник до фрейму даних, він перетворить словник спершу у Series та потім додає. (Так повторюється та ж проблема)

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)
Майк T

2

Щоразу, коли ви отримуєте дані з фрейму даних або додаєте дані до фрейму даних та потребуєте збереження типу даних однаково, уникайте перетворення в інші внутрішні структури, які не знають про необхідні типи даних.

Коли ви df.loc[0]це перетворюєте pd.Series,

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

А тепер, Seriesбуде лише один dtype. Таким чином примушуючи intдо float.

Замість того, щоб зберегти структуру , як 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функцію pandas ,

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

Інший підхід з незначними маніпуляціями з даними:

Припустимо, у вас є список словників (або фреймів даних)

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

Отже, якщо у вас є фрейм даних та перелік диктів, ви можете просто використовувати

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

0

У першому випадку ви можете працювати з нульовим цілим типом даних . Вибір серії не примушує 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.