Prevenire la coercizione dei frame di dati Panda durante l'indicizzazione e l'inserimento di righe


16

Sto lavorando con singole file di frame di dati Panda, ma inciampo su problemi di coercizione durante l'indicizzazione e l'inserimento di righe. I panda sembrano voler sempre forzare da un misto int / float a tutti i tipi float, e non riesco a vedere alcun controllo ovvio su questo comportamento.

Ad esempio, ecco un semplice frame di dati con aas inte 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

Ecco un problema di coercizione durante l'indicizzazione di una riga:

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}

Ed ecco un problema di coercizione durante l'inserimento di una riga:

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 entrambi i casi, voglio che la acolonna rimanga come un numero intero, piuttosto che essere costretto a un tipo float.


Ho trovato questo , ma non sono riuscito a trovare se efficacemente il problema è stato risolto. Nel frattempo immagino che potresti fare:df.loc[[0], df.columns]
Dani Mesejo,


Sembra che pd.DataFrame non supporti il ​​missaggio dei tipi all'istanza? pandas.pydata.org/pandas-docs/stable/reference/api/… dtype param supporta solo un singolo tipo. .read_[type]supporta più tipi diversi ...
Quentin,

Risposte:


4

Dopo alcuni scavi, ecco alcune soluzioni terribilmente brutte. (Sarà accettata una risposta migliore.)

Una stranezza trovata qui è che le colonne non numeriche interrompono la coercizione, quindi ecco come indicizzare una riga in un dict:

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

E l'inserimento di una riga può essere fatto creando un nuovo frame di dati con una riga:

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

Entrambi questi trucchi non sono ottimizzati per i frame di dati di grandi dimensioni, quindi apprezzerei molto una risposta migliore!


Potresti sempre forzare post append df['a'] = df.a.astype(mytype)... È comunque sporco e probabilmente non efficiente.
Quentin,

.astype()è pericoloso per float -> intero; essa non ha alcun problema cambiando 1.1a 1, quindi si ha realmente bisogno per essere sicuri che tutti i valori sono 'intero-like' prima di farlo. Probabilmente è meglio usarlo pd.to_numericcondowncast='integer'
ALollz l'

2

La radice del problema è che

  1. L'indicizzazione del frame di dati di Panda restituisce una serie di Panda

Possiamo vederlo:

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

E una serie può avere un solo tipo, nel tuo caso int64 o float64.

Mi vengono in mente due soluzioni alternative:

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}

o

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. Quando si aggiunge un dizionario a un dataframe, prima convertirà il dizionario in una serie , quindi verrà aggiunto. (Quindi lo stesso problema si ripete)

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

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

Quindi il tuo walkaround è in realtà solido, altrimenti potremmo:

df.append(pd.Series({'a': 5, 'b': 4.4}, dtype=object, name=1))
#    a    b
# 0  1  2.2
# 1  5  4.4

Buona idea usare objecttipi di dati! Un altro è quello di creare un oggetto DataFrame dall'inizio:df = pd.DataFrame({'a': [1], 'b': [2.2]}, dtype=object)
Mike T,

2

Ogni volta che si ottengono dati da un frame di dati o si aggiungono dati a un frame di dati e si deve mantenere lo stesso tipo di dati, evitare la conversione in altre strutture interne che non sono a conoscenza dei tipi di dati necessari.

Quando lo fai df.loc[0]si converte in pd.Series,

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

E ora, Seriesavrà solo un singolo dtype. Così costringendo intafloat .

Invece mantieni la struttura come pd.DataFrame,

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

Seleziona la riga necessaria come cornice e poi converti in dict

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

Allo stesso modo, per aggiungere una nuova riga, usa la pd.DataFrame.appendfunzione panda ,

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

Quanto sopra non causerà la conversione del tipo,

>>> df.dtypes
a      int64
b    float64
dtype: object

Wow ha dovuto leggere quel secondo blocco di codice tre volte per ottenerlo. È molto sottile. Questo è molto meglio di quello che ho fatto in passato ... esegui il ciclo del frame di dati finale e riassegna i valori con il tipo di dati corretto (sì, quello che ho fatto è una soluzione orribile che in realtà non si ridimensionerà).
VanBantam,

1
Oh. Sono contento che abbia aiutato 😊 @VanBantam
Vishnudev l'

1

Un approccio diverso con lievi manipolazioni dei dati:

Supponiamo di avere un elenco di dizionari (o frame di dati)

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

dove ogni dizionario rappresenta una riga (notare gli elenchi nel secondo dizionario). Quindi è possibile creare facilmente un frame di dati tramite:

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

e mantieni i tipi di colonne. Vedi concat

Quindi se hai un dataframe e un elenco di dicts, potresti semplicemente usare

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

0

Nel primo caso, è possibile lavorare con il tipo di dati integer nullable . La selezione della serie non viene forzata floate i valori vengono inseriti in un objectcontenitore. Il dizionario viene quindi creato correttamente, con il valore sottostante memorizzato come a 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

Con la tua sintassi, questo funziona quasi anche per il secondo caso, ma questo aumenta object, quindi non eccezionale:

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

Tuttavia, possiamo apportare una piccola modifica alla sintassi per aggiungere una riga alla fine (con un RangeIndex) e ora i tipi vengono trattati correttamente.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.