2014-09-11 20 views
15

Podczas zapisywania elementu DataFrame Pandas w pliku csv niektóre liczby całkowite są konwertowane na wartości zmiennoprzecinkowe. Zdarza się, że w kolumnie wartości pływających brakuje wartości (np.nan).Eksportowanie danych z brakującymi wartościami do pliku CSV w Pandach

Czy istnieje prosty sposób na uniknięcie tego? (Zwłaszcza w sposób automatyczny. - I często do czynienia z wieloma kolumnami różnych typów danych)

Na przykład

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

plony

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

Co chciałbym dostać to

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

EDYCJA: Jestem w pełni świadomy Support for integer NA - Pandas Caveats and Gotchas. Pytanie brzmi: jakie jest miłe obejście (szczególnie w przypadku, gdy istnieje wiele innych kolumn różnych typów i nie wiem z góry, które kolumny "integer" mają brakujące wartości).

+2

Dlaczego jest to problem , nie ma sposobu na reprezentowanie 'NaN' dla ints stąd konwersja na floats. Musiałbyś zastąpić wartości 'NaN' czymś, co może być reprezentowane jako int,' 0', lub przekonwertować na ciąg znaków i zamienić łańcuch 'nan' z pustą wartością, a następnie wyeksportować – EdChum

+1

@EdChum Wiem, że' NaN' są spławikami. Tylko irytujące jest to, że nie ma "brakującego int" (z perspektywy danych - brakujące pole jest brakującym polem, nie ma nic szczególnego w brakujących elementach pływających). Chodzi o to, że nie chcę eksportować brakującego int jako '0', ale jako puste pole (dla niektórych aplikacji konwertuję brakujące int do' -1', ale dla innych może to być problematyczne). –

+0

@PiotrMigdal Myślę, że twój jedyny strzał w tym przypadku to zamiana na ciągi i wypełnienie nan pustymi ciągami, jak już zasugerowałem – Korem

Odpowiedz

5

Korzystanie float_format = '%.12g' wewnątrz funkcja to_csv rozwiązał podobny problem dla mnie.Utrzymuje dziesiętne dla legalnych pływaków z maksymalnie 12 cyfr znaczących, ale spada ich do ints zmuszany do pływaków obecnością 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') 

wyjścia:

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

sugestia @EdChum „s jest komentarz jest miły, można również użyć float_format argumentu (patrz w 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') 

rozdaje:

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

Ogólnie mam wiele kolumn. Nie chcę formatować "normalnych zmiennych" jako '% .0f''. Chcę tylko sformatować 'int' (zmieszany z' np.nan's, które są niestety pływające) jako '% .0f''. –

4

mam rozszerzając Przykładowe dane tutaj, aby upewnić się, mam nadzieję, że to jest obsługa sytuacji mamy do czynienia z:

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 

Myślę, że jeśli chcesz ogólnego rozwiązania, będzie to musiało być jawnie zakodowane z powodu pand, które nie pozwalają naNs w int kolumnach. To, co tutaj zrobię, to sprawdzanie liczb całkowitych wartości (ponieważ nie możemy naprawdę sprawdzić typu, ponieważ zostaną przekształcone na pływające, jeśli zawierają NaN), a jeśli jest to wartość całkowita, to przekonwertuj na format ciągu, a także przelicz 'NAN' na '' (pusty). Oczywiście nie jest tak, że chcesz przechowywać liczby całkowite za wyjątkiem ostatniego kroku przed wyprowadzeniem.

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

Oto plik wyjściowy, a także, jak to wygląda, jeśli czytać ją z powrotem do pand choć cel ten jest prawdopodobnie czytać go do innych pakietów liczbowych.

%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

Dzięki! Ma sens; mimo to, wciąż wymusza na floats wartości będące liczbami całkowitymi do int (rozważ kolumnę z wartościami [1.0, -5.0, 3.0] '). Jednak widzę, że dodanie jednego rodzaju zmian typu np.nan' powoduje, że nie ma możliwości odzyskania oryginału. :/W tym przypadku jestem ciekawy, czy można uniknąć rzucania kolumnami (np. Mając typ "obiektowy" i mieszany typ elementów). EDIT: Wydaje się, że ustawienie 'dtype = 'object'' podczas tworzenia' DataFrame' lub 'low_memory = False' robi lewę. –

+0

@PiotrMigdal Wystarczy edytować, spójrz. Pierwsza część pytania powinna zostać naprawiona z dodatkiem 'if any (df [col] .isnull()):' (również dodano nową kolumnę). Nie rozumiem drugiej części pytania. Możliwa jest mieszanie tylko obiektów, przechowywanie liczb jako obiektów powinno być zawsze ostatecznością (ale na przykład tak, jak sądzę), ponieważ wydajność liczbowa będzie znacznie gorsza w przypadku obiektów niż int/floats. – JohnE

5

Ten fragment robi to, czego oczekujesz i powinien być stosunkowo wydajny.

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) 

Możemy to sprawdzić w prosty DataFrame który powinien obejmować wszystkie zasady:

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) 

która produkuje następujące csv plik:

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

Działa, ale jaka jest rola '.fillna (0)'? Wydaje się zbędne. –

+0

To dlatego, że konwersja do 'int64' nie działa, jeśli masz' nan's w kolumnie. (Chociaż jeśli zadziała bez niego, może wyjmę to ...) – LondonRob

Powiązane problemy