2014-09-11 24 views
15

Quando si salva un DataFrame di Pandas in csv, alcuni numeri interi vengono convertiti in float. Succede dove una colonna di float ha valori mancanti (np.nan).Esportazione di interi con valori mancanti in csv in Pandas

C'è un modo semplice per evitarlo? (Specialmente in modo automatico -. Ho a che fare spesso con molte colonne di vari tipi di dati)

Per esempio

import pandas as pd 
import numpy as np 
df = pd.DataFrame([[1,2],[3,np.nan],[5,6]], 
        columns=["a","b"], 
        index=["i_1","i_2","i_3"]) 
df.to_csv("file.csv") 

rendimenti

,a,b 
i_1,1,2.0 
i_2,3, 
i_3,5,6.0 

Quello che vorrei ottenere è

,a,b 
i_1,1,2 
i_2,3, 
i_3,5,6 

MODIFICA: Sono pienamente a conoscenza di Support for integer NA - Pandas Caveats and Gotchas. La domanda è quale sia una buona soluzione (soprattutto nel caso in cui ci siano molte altre colonne di vario tipo e non so in anticipo quali colonne "integer" abbiano valori mancanti).

+2

Perché questo è un problema , non c'è modo di rappresentare 'NaN' per ints quindi la conversione in float. Dovresti sostituire i valori di 'NaN' con qualcosa che può essere rappresentato come un int come' 0', o convertire in stringa e sostituire la stringa 'nan' con un valore vuoto e quindi esportare – EdChum

+1

@EdChum So che' NaN' sono galleggianti. Solo è fastidioso che non ci sia "missing int" (dal punto di vista dei dati - un campo mancante è un campo mancante, non c'è nulla di speciale nella mancanza dei float). Il fatto è che non voglio esportare un int mancante come '0', ma come campo vuoto (per alcune applicazioni converto gli interi mancanti a' -1', ma per altri potrebbe essere problematico). –

+0

@PiotrMigdal Penso che il tuo unico scatto qui in quel caso sia convertire in stringhe e riempire nan con stringhe vuote, come già suggerito – Korem

risposta

5

Utilizzando float_format = '%.12g' all'interno della funzione to_csv ha risolto un problema simile per me.Mantiene i decimali per carri legittimi con un massimo di 12 cifre significative, ma li lascia cadere per interi costretti a carri per la presenza di Nan:

In [4]: df 
Out[4]: 
    a b 
i_1 1 2.0 
i_2 3 NaN 
i_3 5.9 6.0 

In [5]: df.to_csv('file.csv', float_format = '%.12g') 

uscita è:

, a, b 
i_1, 1, 2 
i_2, 3, 
i_3, 5.9, 6 
2

suggerimento @EdChum s' è il commento è bello, si potrebbe anche usare l'argomento float_format (vedere nel docs)

In [28]: a 
Out[28]: 
    a b 
0 0 1 
1 1 NaN 
2 2 3 
In [31]: a.to_csv(r'c:\x.csv', float_format = '%.0f') 

dà fuori:

,a,b 
0,0,1 
1,1, 
2,2,3 
+0

In generale ho molte colonne. Non voglio formattare "normali float" come ''% .0f''. Voglio solo formattare 'int' (mescolato con' np.nan's, che sono purtroppo float) come ''% .0f''. –

4

sto ampliando la esempi di dati qui per sperare che questo stia gestendo le situazioni con cui hai a che fare:

df = pd.DataFrame([[1.1,2,9.9,44,1.0], 
        [3.3,np.nan,4.4,22,3.0], 
        [5.5,8,np.nan,66,4.0]], 
        columns=list('abcde'), 
        index=["i_1","i_2","i_3"]) 

     a b c d e 
i_1 1.1 2 9.9 44 1 
i_2 3.3 NaN 4.4 22 3 
i_3 5.5 8 NaN 66 4 

df.dtypes 

a float64 
b float64 
c float64 
d  int64 
e float64 

Penso che se si desidera una soluzione generale, sarà necessario codificarla esplicitamente a causa del fatto che i panda non consentono i NaN nelle colonne int. Quello che faccio qui sotto è controllare i numeri interi valori (dato che non possiamo davvero controllare il tipo in quanto saranno stati convertiti in float se contengono NaNs), e se è un valore intero, allora converti in un formato stringa e anche convertire 'NAN' a '' (vuoto). Ovviamente, questo non è il modo in cui si desidera memorizzare gli interi, ad eccezione di un passaggio finale prima dell'output.

for col in df.columns: 
    if any(df[col].isnull()): 
     tmp = df[col][ df[col].notnull() ] 
     if all(tmp.astype(int).astype(float) == tmp.astype(float)): 
      df[col] = df[col].map('{:.0F}'.format).replace('NAN','') 

df.to_csv('x.csv') 

Ecco il file di output e anche quello che sembra, se lo si legge di nuovo nel panda, anche se lo scopo di questo è presumibilmente per leggerlo in altri pacchetti numerici.

%more x.csv 

,a,b,c,d,e 
i_1,1.1,2,9.9,44,1.0 
i_2,3.3,,4.4,22,3.0 
i_3,5.5,8,,66,4.0 

pd.read_csv('x.csv') 

    Unnamed: 0 a b c d e 
0  i_1 1.1 2 9.9 44 1 
1  i_2 3.3 NaN 4.4 22 3 
2  i_3 5.5 8 NaN 66 4 
+0

Grazie! Ha senso; tuttavia, continua a forzare i float che hanno valori interi in interi (si consideri una colonna con valori '[1.0, -5.0, 3.0]'). Eppure, quello che vedo è che l'aggiunta di un singolo 'np.nan' cambia tipo di tutte le voci, quindi non c'è possibilità di recuperare l'originale. :/In questo caso sono curioso di sapere se è possibile evitare il casting di colonne (ad esempio con tipi 'object' e tipi misti di elementi). EDIT: Sembra che l'impostazione 'dtype = 'object'' quando si crea un' DataFrame' o 'low_memory = False' faccia il trucco. –

+0

@PiotrMigdal Appena modificato, dai un'occhiata. La prima parte della domanda dovrebbe essere risolta con l'aggiunta di 'if any (df [col] .isnull()):' (ha anche aggiunto una nuova colonna). Non capisco la seconda parte della domanda. Solo gli oggetti possono essere mescolati, memorizzando i numeri in quanto gli oggetti dovrebbero sempre essere l'ultima risorsa (per caso come questo, immagino) perché le prestazioni numeriche saranno molto peggiori con gli oggetti di ints/float. – JohnE

5

Questo frammento fa quello che vuoi e dovrebbe essere relativamente efficiente nel farlo.

import numpy as np 
import pandas as pd 

EPSILON = 1e-9 

def _lost_precision(s): 
    """ 
    The total amount of precision lost over Series `s` 
    during conversion to int64 dtype 
    """ 
    try: 
     return (s - s.fillna(0).astype(np.int64)).sum() 
    except ValueError: 
     return np.nan 

def _nansafe_integer_convert(s): 
    """ 
    Convert Series `s` to an object type with `np.nan` 
    represented as an empty string "" 
    """ 
    if _lost_precision(s) < EPSILON: 
     # Here's where the magic happens 
     as_object = s.fillna(0).astype(np.int64).astype(np.object) 
     as_object[s.isnull()] = "" 
     return as_object 
    else: 
     return s 


def nansafe_to_csv(df, *args, **kwargs): 
    """ 
    Write `df` to a csv file, allowing for missing values 
    in integer columns 

    Uses `_lost_precision` to test whether a column can be 
    converted to an integer data type without losing precision. 
    Missing values in integer columns are represented as empty 
    fields in the resulting csv. 
    """ 
    df.apply(_nansafe_integer_convert).to_csv(*args, **kwargs) 

Possiamo testare questo con un semplice dataframe che dovrebbe coprire tutte le basi:

In [75]: df = pd.DataFrame([[1,2, 3.1, "i"],[3,np.nan, 4.0, "j"],[5,6, 7.1, "k"]] 
        columns=["a","b", "c", "d"], 
        index=["i_1","i_2","i_3"]) 
In [76]: df 
Out[76]: 
    a b c d 
i_1 1 2 3.1 i 
i_2 3 NaN 4.0 j 
i_3 5 6 7.1 k 

In [77]: nansafe_to_csv(df, 'deleteme.csv', index=False) 

che produce il csv seguente file:

a,b,c,d 
1,2,3.1,i 
3,,4.0,j 
5,6,7.1,k 
+0

Funziona, ma qual è il ruolo di '.fillna (0)'? Sembra ridondante. –

+0

È perché la conversione in 'int64' non funziona se si ha' nan's nella colonna. (Anche se funziona senza, forse lo tirerò fuori ...) – LondonRob