Verhindern Sie das Erzwingen von Pandas-Datenrahmen beim Indizieren und Einfügen von Zeilen

16

Ich arbeite mit einzelnen Zeilen von Pandas-Datenrahmen, aber ich stolpere über Zwangsprobleme beim Indizieren und Einfügen von Zeilen. Pandas scheint immer von einem gemischten int / float-Typ zu einem All-float-Typ zwingen zu wollen, und ich kann keine offensichtlichen Kontrollen für dieses Verhalten erkennen.

Hier ist zum Beispiel ein einfacher Datenrahmen mit aas intund 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

Hier ist ein Zwangsproblem beim Indizieren einer Zeile:

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}

Und hier ist ein Zwangsproblem beim Einfügen einer Zeile:

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

In beiden Fällen möchte ich, dass die aSpalte als Ganzzahltyp verbleibt und nicht zu einem Float-Typ gezwungen wird.

Mike T.
quelle
Ich fand dies , konnte aber nicht feststellen, ob das Problem effektiv gelöst wurde. In der Zwischenzeit könnten Sie tun:df.loc[[0], df.columns]
Dani Mesejo
Klingt so, als ob pd.DataFrame das Mischen von Typen bei der Instanziierung nicht unterstützt? pandas.pydata.org/pandas-docs/stable/reference/api/… dtype param unterstützt nur einen einzigen Typ. .read_[type]unterstützt jedoch mehrere dtypes ...
Quentin

Antworten:

4

Nach einigem Graben finden Sie hier einige schrecklich hässliche Problemumgehungen. (Eine bessere Antwort wird akzeptiert.)

Eine Besonderheit, die hier zu finden ist, ist, dass nicht numerische Spalten den Zwang stoppen. Hier erfahren Sie , wie Sie eine Zeile auf a indizieren dict:

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

Zum Einfügen einer Zeile können Sie einen neuen Datenrahmen mit einer Zeile erstellen:

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

Beide Tricks sind nicht für große Datenrahmen optimiert, daher würde ich mich über eine bessere Antwort sehr freuen!

Mike T.
quelle
Sie könnten immer nur das Anhängen von Posts erzwingen df['a'] = df.a.astype(mytype)... Es ist zwar immer noch schmutzig und wahrscheinlich nicht effizient.
Quentin
.astype()ist gefährlich für float -> integer; es hat kein Problem Wechsel 1.1zu 1, so dass Sie wirklich sicher sein müssen , alle Ihre Werte sind ‚integer-like‘ , bevor es zu tun. Wahrscheinlich am besten pd.to_numericmitdowncast='integer'
ALollz
2

Die Wurzel des Problems ist das

  1. Die Indizierung des Pandas-Datenrahmens gibt eine Pandas-Reihe zurück

Wir können das sehen:

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

Und eine Serie kann nur einen dtype haben, in Ihrem Fall entweder int64 oder float64.

Ich habe zwei Problemumgehungen:

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}

oder

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. Wenn Sie ein Wörterbuch an einen Datenrahmen anhängen, wird das Wörterbuch zuerst in eine Serie konvertiert und dann angehängt. (Also passiert das gleiche Problem wieder)

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

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

Ihr Walkaround ist also tatsächlich solide, oder wir könnten:

df.append(pd.Series({'a': 5, 'b': 4.4}, dtype=object, name=1))
#    a    b
# 0  1  2.2
# 1  5  4.4
Hongpei
quelle
Gute Idee, objectDatentypen zu verwenden! Eine andere Möglichkeit besteht darin, von Anfang an ein Objekt DataFrame zu erstellen:df = pd.DataFrame({'a': [1], 'b': [2.2]}, dtype=object)
Mike T
2

Vermeiden Sie die Konvertierung in andere interne Strukturen, die die benötigten Datentypen nicht kennen, wenn Sie Daten aus dem Datenrahmen abrufen oder Daten an einen Datenrahmen anhängen und den Datentyp beibehalten müssen.

Wenn Sie es tun df.loc[0], konvertiert es zu pd.Series,

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

Und jetzt Serieswird nur eine einzige haben dtype. So zwingen intzufloat .

Behalten Sie stattdessen die Struktur bei pd.DataFrame,

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

Wählen Sie die als Frame benötigte Zeile aus und konvertieren Sie sie dann in dict

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

Um eine neue Zeile hinzuzufügen, verwenden Sie die Pandas- pd.DataFrame.appendFunktion.

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

Dies führt nicht zu einer Typkonvertierung.

>>> df.dtypes
a      int64
b    float64
dtype: object
Vishnudev
quelle
Wow musste diesen zweiten Codeblock dreimal lesen, um ihn zu bekommen. Das ist sehr subtil. Dies ist viel besser als das, was ich in der Vergangenheit getan habe ... Durchlaufen Sie den endgültigen Datenrahmen und weisen Sie die Werte dem richtigen Datentyp zu (ja, was ich getan habe, ist eine schreckliche Lösung, die sich wirklich nicht skalieren lässt.).
VanBantam
1
Oh. Ich bin froh, dass es geholfen hat 😊 @VanBantam
Vishnudev
1

Ein anderer Ansatz mit geringfügigen Datenmanipulationen:

Angenommen, Sie haben eine Liste von Wörterbüchern (oder Datenrahmen).

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

Dabei steht jedes Wörterbuch für eine Zeile (beachten Sie die Listen im zweiten Wörterbuch). Dann können Sie ganz einfach einen Datenrahmen erstellen über:

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

und Sie pflegen die Arten der Spalten. Siehe concat

Wenn Sie also einen Datenrahmen und eine Liste von Diktaten haben, können Sie diese einfach verwenden

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

Im ersten Fall können Sie mit dem Datentyp nullable integer arbeiten . Die Serienauswahl wird nicht erzwungen floatund Werte werden in einen objectContainer gestellt. Das Wörterbuch wird dann ordnungsgemäß erstellt, wobei der zugrunde liegende Wert als gespeichert wird 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

Mit Ihrer Syntax funktioniert dies fast auch für den zweiten Fall, aber dies wird aktualisiert object, also nicht großartig:

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

Wir können jedoch eine kleine Änderung an der Syntax zum Hinzufügen einer Zeile am Ende vornehmen (mit einem RangeIndex), und jetzt werden Typen ordnungsgemäß behandelt.

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
quelle