2014-09-11 8 views
15

Beim Speichern eines Pandas DataFrame in CSV werden einige Ganzzahlen in Gleitkommazahlen konvertiert. Es passiert, wo eine Spalte von Floats fehlende Werte hat (np.nan).Exportieren von Ints mit fehlenden Werten in CSV in Pandas

Gibt es eine einfache Möglichkeit, dies zu vermeiden? (Vor allem in einer automatischen Art und Weise -. Ich habe oft mit vielen Spalten verschiedenen Datentypen befassen)

Zum Beispiel

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

Erträge

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

Was würde ich bekommen sind

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

EDIT: Ich bin voll bewusst von Support for integer NA - Pandas Caveats and Gotchas. Die Frage ist, was ist eine nette Abhilfe (vor allem in dem Fall, wenn es viele andere Spalten verschiedener Typen gibt und ich nicht im Voraus weiß, welche "Integer" -Spalten fehlende Werte haben).

+2

Warum ist das ein Problem , es gibt keine Möglichkeit, 'NaN' für Ints darzustellen, daher die Umwandlung in Floats. Sie müssten die 'NaN'-Werte durch etwas ersetzen, das als ein int wie' 0' dargestellt werden kann, oder in eine Zeichenfolge umwandeln und die 'nan'-Zeichenfolge durch einen leeren Wert ersetzen und dann exportieren – EdChum

+1

@EdChum NaN' sind Schwimmer. Es ist einfach ärgerlich, dass es kein "fehlendes int" gibt (aus der Datenperspektive - ein fehlendes Feld ist ein fehlendes Feld; an fehlenden Floats gibt es nichts Besonderes). Die Sache ist, dass ich kein fehlendes int als '0' exportieren möchte, sondern ein leeres Feld (für einige Anwendungen konvertiere ich fehlende Ints in '-1', aber für andere könnte es problematisch sein). –

+0

@PiotrMigdal Ich denke, deine einzige Chance hier in diesem Fall ist es, in Strings zu konvertieren und Nan mit leeren Zeichenfolgen zu füllen, wie bereits vorgeschlagen – Korem

Antwort

5

float_format = '%.12g' Verwendung innerhalb der to_csv Funktion eines gelösten ähnliches Problem für mich.Es hält die Dezimalstellen für legitimen Schwimmer mit bis zu 12 signifikanten Stellen, aber fällt sie für ints durch die Anwesenheit von Nans zu Schwimmern gezwungen werden:

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

Ausgang ist:

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

@EdChum ‚s Vorschlag ist der Kommentar schön ist, können Sie auch das float_format Argument verwenden (siehe im 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') 

Gibt heraus:

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

Im Allgemeinen habe ich viele Spalten. Ich möchte "normal floats" nicht als "% .0f" formatieren. Ich möchte nur 'int' (gemischt mit' np.nan's, die leider schweben) als ''% .0f'' formatieren. –

4

Ich erweitere die Beispieldaten hier hoffentlich sicherzustellen, dass dies die Situation ist der Umgang mit Ihnen beschäftigen uns mit:

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 

Ich denke, wenn Sie eine allgemeine Lösung wollen, muss es explizit codiert werden, da Pandas NaNs in int-Spalten nicht zulassen. Was ich hier unten tue, ist die Suche nach Ganzzahlen Werte (da wir den Typ nicht wirklich überprüfen können, da sie in Float umgewandelt werden, wenn sie NaNs enthalten), und wenn es ein ganzzahliger Wert ist, dann konvertiere in ein Stringformat wandle 'NAN' in '' (leer) um. Natürlich möchten Sie die Ganzzahlen nicht speichern, außer als letzten Schritt vor der Ausgabe.

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

Hier ist die Ausgabedatei und auch, wie es aussieht, wenn Sie es zurück in die Pandas lesen, obwohl der Zweck dieser vermutlich ist es in andere numerische Pakete zu lesen.

%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

Danke! Macht Sinn; obwohl er immer noch floats, die Integer-Werte zu Ints haben, erzwingt (betrachte eine Spalte mit Werten '[1.0, -5.0, 3.0]'). Aber was ich sehe, ist, dass das Hinzufügen eines einzigen 'np.nan' die Typen aller Einträge ändert, so dass es keine Möglichkeit gibt, das Original wiederherzustellen. :/In diesem Fall bin ich neugierig, ob es möglich ist, das Spaltencasting zu vermeiden (z. B. mit "Objekt" -Typ und gemischten Typen von Elementen). EDIT: Scheint, dass die Einstellung 'dtype = 'object'' beim Erstellen eines' DataFrame' oder 'low_memory = False' den Trick macht. –

+0

@PiotrMigdal Gerade bearbeitet, werfen Sie einen Blick. Der erste Teil der Frage sollte durch Hinzufügen von "falls vorhanden" (df [col] .isnull()): '(auch eine neue Spalte hinzugefügt) behoben werden. Ich verstehe den zweiten Teil der Frage nicht. Nur Objekte können gemischt werden, das Speichern von Zahlen als Objekte sollte immer der letzte Ausweg sein (für den Fall wie dies, denke ich), weil die numerische Leistung bei Objekten viel schlechter sein wird als bei Ints/Floats. – JohnE

5

Dieses Snippet macht was Sie wollen und sollte relativ effizient sein.

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) 

Wir dies mit einem einfachen Datenrahmen testen, die alle Basen abdecken sollen:

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) 

, das die folgende csv Datei erzeugt:

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

Works, aber was ist die Rolle von '.fillna (0)'? Es scheint überflüssig. –

+0

Das liegt daran, dass die Konvertierung zu 'int64' nicht funktioniert, wenn Sie' nan' in der Spalte haben. (Obwohl, wenn es ohne es funktioniert, vielleicht werde ich es herausnehmen ...) – LondonRob

Verwandte Themen