Экспорт ints с отсутствующими значениями в csv в Pandas
При сохранении Pandas DataFrame в csv некоторые целые числа преобразуются в поплавки.
Это происходит, когда столбец с поплавками имеет отсутствующие значения (np.nan
).
Есть ли простой способ избежать этого?
(Особенно в автоматическом режиме - я часто имею дело со многими столбцами различных типов данных.)
Например
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")
дает
,a,b
i_1,1,2.0
i_2,3,
i_3,5,6.0
То, что я хотел бы получить,
,a,b
i_1,1,2
i_2,3,
i_3,5,6
EDIT: я полностью осведомлен о поддержке целых NA - Pandas Предостережений и Gotchas. Вопрос в том, что является хорошим обходным решением (особенно в случае, если имеется много других столбцов разных типов, и я заранее не знаю, какие столбцы "целочисленные" имеют отсутствующие значения).
Ответы
Ответ 1
Использование float_format = '%.12g'
внутри функции to_csv разрешило аналогичную проблему для меня. Он хранит десятичные знаки для законных поплавков с 12 значащими цифрами, но бросает их для того, чтобы ints был вынужден плавать при наличии NaN's:
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')
Выход:
, a, b
i_1, 1, 2
i_2, 3,
i_3, 5.9, 6
Ответ 2
Этот фрагмент делает то, что вы хотите, и должен быть относительно эффективным при этом.
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 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)
Мы можем проверить это с помощью простого DataFrame, который должен охватывать все базы:
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)
Создает следующий csv
файл:
a,b,c,d
1,2,3.1,i
3,,4.0,j
5,6,7.1,k
Ответ 3
Я расширяю образцы данных здесь, чтобы надеяться убедиться, что это обрабатывает ситуации, с которыми вы имеете дело:
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
Я думаю, что если вы хотите общее решение, оно должно быть явно закодировано из-за pandas, не позволяя NaNs в столбцах int. То, что я делаю ниже, - это проверка значений целых чисел (поскольку мы не можем действительно проверить тип, поскольку они будут перепрограммированы для float, если они содержат NaN), и если это целочисленное значение, то преобразование в строковый формат, а также преобразование 'NAN'
до ''
(пустой). Конечно, это не то, как вы хотите хранить целые числа, за исключением последнего шага перед выводом.
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')
Вот выходной файл, а также то, на что он похож, если вы прочитали его обратно в pandas, хотя цель этого, по-видимому, читать его в другие числовые пакеты.
%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
Ответ 4
@EdChum предложение - комментарий хороший, вы также можете использовать аргумент float_format
(см. 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')
Выдает:
,a,b
0,0,1
1,1,
2,2,3