Zapobiegaj przymusowi ramek danych pand podczas indeksowania i wstawiania wierszy

16

Pracuję z pojedynczymi wierszami ramek danych pand, ale natrafiam na problemy z przymusem podczas indeksowania i wstawiania wierszy. Wydaje się, że Pandy zawsze chcą wymuszać mieszane typy int / float na all-float i nie widzę żadnych oczywistych kontroli tego zachowania.

Na przykład tutaj jest prosta ramka danych z ajak inti bjako 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

Oto problem dotyczący przymusu podczas indeksowania jednego wiersza:

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}

A oto kwestia przymusu podczas wstawiania jednego wiersza:

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

W obu przypadkach chcę, aby akolumna pozostała typem całkowitym, a nie wymuszona na typ zmiennoprzecinkowy.

Mike T.
źródło
Znalazłem to , ale nie mogłem znaleźć, jeśli skutecznie problem został rozwiązany. W międzyczasie wydaje mi się, że możesz:df.loc[[0], df.columns]
Dani Mesejo,
Wygląda na to, że pd.DataFrame nie obsługuje miksowania typów przy tworzeniu instancji? pandas.pydata.org/pandas-docs/stable/reference/api/… parametr dtype obsługuje tylko jeden typ. .read_[type]obsługuje wiele dtypów ...
Quentin,

Odpowiedzi:

4

Po kilku kopaniach, oto kilka strasznie brzydkich obejść. (Lepsza odpowiedź zostanie zaakceptowana.)

Dziwactwo znalezione tutaj polega na tym, że kolumny nienumeryczne zatrzymują przymus, więc oto jak zindeksować jeden wiersz do dict:

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

A wstawianie wiersza można wykonać, tworząc nową ramkę danych z jednym wierszem:

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

Obie te sztuczki nie są zoptymalizowane dla dużych ramek danych, więc byłbym bardzo wdzięczny za lepszą odpowiedź!

Mike T.
źródło
Zawsze możesz po prostu przymusić dodawanie posta df['a'] = df.a.astype(mytype)... Jest jednak wciąż brudny i prawdopodobnie nieskuteczny.
Quentin,
.astype()jest niebezpieczny dla float -> liczba całkowita; nie ma problemu ze zmianą 1.1na 1, więc naprawdę musisz upewnić się, że wszystkie twoje wartości są „liczbami całkowitymi” zanim to zrobisz. Prawdopodobnie najlepiej używać pd.to_numericzdowncast='integer'
ALollz
2

Źródłem problemu jest to

  1. Indeksowanie ramki danych pand zwraca serię pand

Widzimy to:

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

A seria może mieć tylko jeden typ, w twoim przypadku int64 lub float64.

Przychodzą mi do głowy dwa obejścia:

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}

lub

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. Kiedy dodasz słownik do ramki danych, najpierw przekonwertuje on słownik na Serię, a następnie dołączy. (Więc ten sam problem zdarza się ponownie)

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

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

Więc twoje obejście jest w rzeczywistości solidne, w przeciwnym razie moglibyśmy:

df.append(pd.Series({'a': 5, 'b': 4.4}, dtype=object, name=1))
#    a    b
# 0  1  2.2
# 1  5  4.4
Hongpei
źródło
Dobry pomysł na użycie objecttypów danych! Innym jest utworzenie obiektu DataFrame od początku:df = pd.DataFrame({'a': [1], 'b': [2.2]}, dtype=object)
Mike T
2

Ilekroć pobierasz dane z ramki danych lub dołączasz dane do ramki danych i musisz zachować ten sam typ danych, unikaj konwersji do innych struktur wewnętrznych, które nie są świadome potrzebnych typów danych.

Kiedy robisz df.loc[0]to konwertuje pd.Series,

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

A teraz Seriesbędzie miał tylko jeden dtype. W ten sposób zmuszając intdo float.

Zamiast tego zachowaj strukturę jako pd.DataFrame,

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

Wybierz wiersz potrzebny jako ramka, a następnie przekonwertuj na dict

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

Podobnie, aby dodać nowy wiersz, użyj pd.DataFrame.appendfunkcji pandy ,

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

Powyższe nie spowoduje konwersji typu,

>>> df.dtypes
a      int64
b    float64
dtype: object
Wisznudew
źródło
Wow musiał przeczytać ten drugi blok kodu trzy razy, aby go zdobyć. To bardzo subtelne. Jest to o wiele lepsze niż to, co robiłem w przeszłości ... zapętlamy ostatnią ramkę danych i ponownie przypisujemy wartości do właściwego typu danych (tak, to co zrobiłem, to okropne rozwiązanie, które tak naprawdę nie będzie skalować).
VanBantam,
1
O. Cieszę się, że pomógł 😊 @VanBantam
Vishnudev
1

Inne podejście z niewielkimi manipulacjami danymi:

Załóżmy, że masz listę słowników (lub ramek danych)

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

gdzie każdy słownik reprezentuje wiersz (zwróć uwagę na listy w drugim słowniku). Następnie możesz łatwo utworzyć ramkę danych za pomocą:

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

i zachowujesz typy kolumn. Zobacz konkat

Więc jeśli masz ramkę danych i listę nagrań, możesz po prostu użyć

pd.concat([df] + [pd.DataFrame(dct) for dct in lod])
Quickbeam2k1
źródło
0

W pierwszym przypadku możesz pracować z typem danych o wartości całkowitej zerowej . Wybór serii nie jest wymagany, floata wartości są umieszczane w objectkontenerze. Słownik jest następnie poprawnie tworzony, a podstawowa wartość jest przechowywana jako 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

W przypadku twojej składni działa to prawie również w drugim przypadku, ale to upcasting do object, więc nie świetne:

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

Możemy jednak wprowadzić niewielką zmianę w składni, aby dodać wiersz na końcu (z RangeIndex), a teraz typy są obsługiwane poprawnie.

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
źródło