Предотвращение приведения кадров данных панд при индексации и вставке строк

16

Я работаю с отдельными строками фреймов данных Pandas, но я спотыкаюсь о проблемах принуждения при индексации и вставке строк. Панды, кажется, всегда хотят привести к смешанному типу int / float к типам с плавающей точкой, и я не вижу каких-либо очевидных элементов управления этим поведением.

Например, вот простой фрейм данных с aas intи 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

Вот проблема приведения при индексации одной строки:

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]
Дани Месехо
Похоже, pd.DataFrame не поддерживает смешивание типов при создании экземпляров? pandas.pydata.org/pandas-docs/stable/reference/api/… dtype param поддерживает только один тип. .read_[type]хотя поддерживает несколько dtypes ...
Квентин

Ответы:

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()опасно для числа с плавающей точкой -> целое число; он не имеет никаких проблем , изменяющийся 1.1в 1, так что вы действительно должны быть уверены , что все ваши ценности «целое число, как» прежде чем делать это. Вероятно, лучше всего использовать pd.to_numericсdowncast='integer'
ALollz
2

Корень проблемы в том, что

  1. Индексация данных в панде возвращает серию панд

Мы видим, что:

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

И у серии может быть только один тип dtype, в вашем случае - 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
Hongpei
источник
Хорошая идея использовать objectтипы данных! Другой - создать объект DataFrame с самого начала:df = pd.DataFrame({'a': [1], 'b': [2.2]}, dtype=object)
Майк Т
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функцию панд ,

>>> 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
Vishnudev
источник
Wow должен был прочитать второй блок кода три раза, чтобы получить его. Это очень тонко. Это намного лучше, чем то, что я делал в прошлом ... перебрать окончательный фрейм данных и переназначить значения с правильным типом данных (да, я сделал ужасное решение, которое действительно не масштабируется).
VanBantam
1
Ой. Рад, что это помогло 😊 @VanBantam
Вишнудев
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

и вы поддерживаете типы столбцов. Смотрите Конкат

Так что если у вас есть датафрейм и список диктовок, вы можете просто использовать

pd.concat([df] + [pd.DataFrame(dct) for dct in lod])
Quickbeam2k1
источник
0

В первом случае вы можете работать с типом данных NULL . Выбор Series не выполняется, 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
ALollz
источник