Предотвращение приведения кадров данных панд при индексации и вставке строк - PullRequest
16 голосов
/ 24 октября 2019

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

Например, вот простой фрейм данных с a as int и b as 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 оставался целочисленным типом, а не приводился к типу с плавающей точкой.

Ответы [ 5 ]

4 голосов
/ 24 октября 2019

После некоторых копаний, вот несколько ужасно уродливых обходных путей. (Лучше ответ будет принят.)

Причудка , найденная здесь , заключается в том, что нечисловые столбцы прекращают приведение, поэтому вот как индексировать одну строку в 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

Оба эти трюка не оптимизированы для больших фреймов данных, поэтому я очень признателенлучший ответ!

2 голосов
/ 09 ноября 2019

Всякий раз, когда вы получаете данные из фрейма данных или добавляете данные в фрейм данных и вам необходимо сохранить тип данных таким же, избегайте преобразования в другие внутренние структуры, которые не знают о необходимых типах данных.

Когда вы делаете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}]

Аналогично, чтобы добавить новую строку, используйте функцию pandas 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
2 голосов
/ 05 ноября 2019

Корень проблемы заключается в том, что

  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}
Когда вы добавляете словарь в фрейм данных, он сначала преобразует словарь в 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
1 голос
/ 08 ноября 2019

Другой подход с небольшими манипуляциями с данными:

Предположим, у вас есть список словарей (или фреймов данных)

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 голосов
/ 07 ноября 2019

В первом случае вы можете работать с типом данных nullable integer . Выбор 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
...