2017-09-15 107 views
1

我有一個recurring problem在Python中將大量數據保存到csv。這些數字是毫秒紀元時間戳,我無法轉換或截斷並必須以此格式保存。由於與毫秒時間戳列還包含一些NaN值,熊貓自動轉換它們漂浮(見陷阱the documentation下「支持整數NA」。Python熊貓大浮動與to_csv

我似乎無法避免這種行爲,所以我的問題是,在使用df.to_csv時,如何將這些數字保存爲整數值,即沒有小數點或尾隨零?我在同一個數據框中有不同浮動精度數的列,我不想丟失那些信息。在to_csv的float_format參數似乎適用相同的格式在我的數據幀ALL浮點列

一個例子:

>>> df = pd.DataFrame({'a':[1.25, 2.54], 'b':[1424380449437, 1425510731187]}) 
>>> df['b'].dtype 
Out[1]: dtype('int64') 
>>> df.loc[2] = np.NaN 
>>> df 
Out[1]: 
     a    b 
0 1.25 1.424380e+12 
1 2.54 1.425511e+12 
2 NaN   NaN 
>>> df['b'].dtype 
dtype('float64') 
>>> df.to_csv('test.csv') 
>>> with open ('test.csv') as f: 
...  for line in f: 
...   print(line) 
,a,b 
0,1.25,1.42438044944e+12 
1,2.54,1.42551073119e+12 
2,, 

正如你所看到的,我失去了我的紀元時間戳的最後兩位數的精度。

+0

您可以用零替換'nan'值,然後將列轉換爲整數。 'df.b = df.b.fillna(0).astype(int)'或在後面的處理中使用'-1'來標識條目。 –

+0

這是一種可能性,但卻是一種相當笨拙的解決方法。我寧願保留'nan'值,因爲它允許簡單的索引和過濾。此外,我用於'nan'的任何佔位符值可能會自然地出現在Dataframe中。 – Alarik

回答

1

儘管pd.to_csv沒有用於更改各列格式的參數,pd.to_string的確如此。這有點麻煩,可能是非常大的DataFrames的一個問題,但是您可以使用它來生成格式正確的字符串,然後將該字符串寫入文件(正如此answer中針對類似問題所建議的那樣)。 to_stringformatters參數以例如格式化各個列的函數字典爲例。在你的情況下,你可以爲"b"列編寫自己的自定義格式化程序,並保留其他列的默認值。這種格式看起來有點像這樣:

def printInt(b): 
    if pd.isnull(b): 
     return "NaN" 
    else: 
     return "{:d}".format(int(b)) 

現在你可以用它來製作你的字符串:

df.to_string(formatters={"b": printInt}, na_rep="NaN") 

這給:

'  a    b\n0 1.25 1424380449437\n1 2.54 1425510731187\n2 NaN   NaN' 

你可以看到,仍有問題,這不是逗號分隔和to_string實際上沒有參數設置自定義分隔符,但這可以很容易地由正則表達式修復:

import re 
re.sub("[ \t]+(NaN)?", ",", 
     df.to_string(formatters={"b": printInt}, na_rep="NaN")) 

給出:

',a,b\n0,1.25,1424380449437\n1,2.54,1425510731187\n2,,' 

這現在可以寫入文件:

with open("/tmp/test.csv", "w") as f: 
    print(re.sub("[ \t]+(NaN)?", ",", 
       df.to_string(formatters={"b": printInt}, na_rep="NaN")), 
      file=f) 

導致你想要的東西:

,a,b 
0,1.25,1424380449437 
1,2.54,1425510731187 
2,, 

如果要保持NaN的在CSV文件,你可以改變的正則表達式:

with open("/tmp/test.csv", "w") as f: 
    print(re.sub("[ \t]+", ",", 
       df.to_string(formatters={"b": printInt}, na_rep="NaN")), 
      file=f) 

會給:

,a,b 
0,1.25,1424380449437 
1,2.54,1425510731187 
2,NaN,NaN 

如果你的數據幀之前,包含有空格的字符串,一個強大的解決方案並不容易。您可以在每個值的前面插入另一個字符,表示下一個條目的開始。如果所有字符串中只有一個空格,則可以使用另一個空格。這將更改代碼這樣:

import pandas as pd 
import numpy as np 
import re 

df = pd.DataFrame({'a a':[1.25, 2.54], 'b':[1424380449437, 1425510731187]}) 
df.loc[2] = np.NaN 

def printInt(b): 
    if pd.isnull(b): 
     return " NaN" 
    else: 
     return " {:d}".format(int(b)) 

def printFloat(a): 
    if pd.isnull(a): 
     return " NaN" 
    else: 
     return " {}".format(a) 

with open("/tmp/test.csv", "w") as f: 
    print(re.sub("[ \t][ \t]+", ",", 
       df.to_string(formatters={"a": printFloat, "b": printInt}, 
           na_rep="NaN", col_space=2)), 
      file=f) 

這將使:

,a a,b 
0,1.25,1424380449437 
1,2.54,1425510731187 
2,NaN,NaN 
+0

我很抱歉,最後的例子不是我想要的,而是我想避免的。所以'NaN's應該被表示爲這樣。另外,如何讓正則表達式不分割包含多個單詞的列名? – Alarik

+0

我更新了我的答案,允許列名中的空格並保留NaN(我以爲你只希望那些留在'df'中)。這有幫助嗎? – jotasi

+0

我接受了你的答案,因爲它解決了我的問題,儘管它仍然是一個可怕的解決方法,主要是因爲我必須爲幾十列指定格式化程序;)我選擇了單個間距正則表達式匹配,因爲雙倍間距會遇到麻煩當列名稱或單元格條目太長時(因爲它只會在列中留下單個空格),因此使用熊貓「to_string」函數。感謝您的幫助! – Alarik

1

也許這可以工作:

pd.set_option('precision',15) 
df = pd.DataFrame({'a':[1.25, 2.54], 'b':[1424380449437, 1425510731187]}) 
fg = df.applymap(lambda x: str(x)) 
fg.loc[2] = np.NaN 
fg.to_csv('test.csv', na_rep='NaN') 

輸出應該是這樣的(我在mac):

enter image description here

+0

但是在這種情況下,您必須在插入'NaN'之前將DataFrames條目更改爲字符串,不是嗎? – jotasi

+2

@jotasi是的,否則你會失去精確度。我想它可能是您的替代品 – erasmortg

+0

謝謝您的輸入,但這對我不起作用 - 我的原始數據框一直有NaN,我只是在示例中添加它們以顯示列的獲取方式力量鑄造浮動。 – Alarik