Python 熊猫通过隐式转换实现整数精度损失



我正在使用许多不同的csv文件作为熊猫数据帧读取,然后从中提取有趣的indies和数据,并将其收集到一个新的数据帧中,我逐行构建然后保存。每行表示一个文件中的信息。

原始数据帧按毫秒精度纪元时间编制索引。虽然时代是不必要的精确,但我无法改变任何事情。

>>> df.index
Int64Index([1382441313687, 1382441314687, 1382441315687, 1382441316687,
1382441317687, 1382441318687, 1382441319687, 1382441320687,
1382441321687, 1382441322687,
...
1382445583687, 1382445584687, 1382445585687, 1382445586687,
1382445587687, 1382445588687, 1382445589687, 1382445590687,
1382445591687, 1382445592687],
dtype='int64', name=u'time', length=4280)

我通过构建一个有趣的值列表并从中创建一系列来构建新的数据帧,然后将其追加到数据帧。

columns = ['Start time', 'End time']
summary = pd.DataFrame(columns=columns)
for i, df in enumerate(long_list_of_dfs):
start_time = df.index[0]
end_time = df.index[-1]
data = [start_time, end_time]
new_line = pd.Series({key:val for key, val in zip(columns, data)})
summary = summary.append(new_line)
summary.to_csv(out_dir)

我使用摘要中保存的 indize 来快速索引原始数据帧中的有趣点。但是,在构建新数据帧时,会丢失一些精度,最终得到以下结果:

>>> for line in open(out_dir):
...     print(line)
,Start time,End time
0,1.38244131369e+12,138244559269e+12

再次阅读此摘要时,我不能再使用这些值来索引原始数据帧,因为它会导致 KeyError。直接构建数据帧时不会发生这种情况:

>>> summary2 = pd.DataFrame({'Start time':[1382441313687], 'End time':[1382445592687]})
>>> summary2
End time     Start time
0  1382445592687  1382441313687
>>> summary2.to_csv(out_dir)
>>> for line in open(out_dir):
...     print(line)
,Start time,End time
0,1382441313687,1382445592687

有谁知道为什么会发生这种转换?我知道我可以指定数据类型,但我有很多具有不同数据类型的列,宁愿省去麻烦。我觉得如果这些值保持原始格式也会更直观。

编辑我想强调的是,我在 for 循环中构建数据帧,因为我每行都想添加许多感兴趣的数据点。此外,原始数据帧的数量相当高(~90.000 个文件 @ 每个 20MB),所以我只想打开每个文件一次。

上面的代码只是一个工作示例,表明尽管数据是整数,但最后两位数字被四舍五入,大概在附加行中。new_line系列的数据仍保留其原始格式,最多为最后两位数字。

下面是前 10 行的 summary.info() 输出。如您所见,有些列包含 NaN,但也有一些不包含。我希望没有 NaN 的列保留其整数格式。

>>> summary.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 88158 entries, 0 to 88157
Data columns (total 46 columns):
Date added            88158 non-null object
Filename              88158 non-null object
ACID                  88158 non-null int64
FLID                  88158 non-null int64
Flag                  88158 non-null object
L ESN                 86986 non-null float64
R ESN                 86986 non-null float64
Start time            88158 non-null float64
End time              88158 non-null float64
Total duration        88158 non-null float64

EDIT2这是另一个简短的示例,用于显示我在使用长整数逐行构建数据帧时的问题。

>>> df = pd.DataFrame(columns=['a', 'b'])
>>> df.loc[len(df.index)] = [1382441313687, 1382441314687]
>>> df
a             b
0  1.382441e+12  1.382441e+12
>>> df.loc[0, 'a']
1382441313687.0 # Correct data!
>>> df.to_csv(out_dir)
>>> for line in open(out_dir):
...     print(line)    
,a,b
0,1.38244131369e+12,1.38244131469e+12 # Not correct! 1382441313690 != 1382441313687

这是因为你附加了一个Series,它有一个dtype,所以如果它包含 1 个float,其他的也会被投射到float

我只能通过稍微调整您的代码来重现您的问题

示例数据生成

columns = ['sample_data']
columns2 = ['Start time', 'End time'] + columns
long_list_of_dfs = [pd.DataFrame(index=[i**2 + j for j in range(i)], columns=columns, data=[j**2 for j in range(i)]) for i in range(5, 15)]

改编的原始代码

summary2 = pd.DataFrame(columns=columns2)
for i, df in enumerate(long_list_of_dfs):
start_time = df.index[0]
end_time = df.index[-1]
data = [df[k].mean() for k in columns]
new_line = pd.Series({key:val for key, val in zip(columns2, [start_time, end_time] + data)}, name=i)
summary2 = summary.append(new_line)
summary2.info()

结果:

<class 'pandas.core.frame.DataFrame'>
Int64Index: 11 entries, 0 to 9
Data columns (total 3 columns):
Start time     11 non-null float64
End time       11 non-null float64
sample_data    11 non-null float64
dtypes: float64(3)
memory usage: 352.0 bytes

new_line

End time       209.0
Start time     196.0
sample_data     58.5
Name: 9, dtype: float64

因此转换发生在追加之前

摘要生成器

防止这种情况的一种方法是 不是为每个原始DataFrame制作Series,而是使用这样的生成器。 这可以是您用于生成所需摘要的任何方法

def get_summary_data(long_list_of_dfs, columns):
for df in long_list_of_dfs:
s = [df[k].mean() for k in columns]
# print(df.index[0], df.index[-1], *s)
yield (df.index[0], df.index[-1], *s)

然后连接

summary = pd.DataFrame(data=get_summary_data(long_list_of_dfs, columns), columns=columns2)

结果

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 3 columns):
Start time     10 non-null int64
End time       10 non-null int64
sample_data    10 non-null float64
dtypes: float64(1), int64(2)
memory usage: 320.0 bytes

总结:

Start time  End time    sample_data
0   25  29  6.000000
1   36  41  9.166667
2   49  55  13.000000
3   64  71  17.500000
4   81  89  22.666667
5   100     109     28.500000
6   121     131     35.000000
7   144     155     42.166667
8   169     181     50.000000
9   196     209     58.500000

可以使用to_csv()导出此DataFrame

我还没有追踪到你的精度损失发生在哪里,但是

summary = pd.DataFrame([(df.index[0], df.index[-1]) for df in long_list_of_dfs],
columns=['Start Time', 'End Time'])

当我尝试它时,工作没有损失,并且与您的摘要2相匹配。

编辑:刚刚看到主要的帖子编辑。

看起来选择单个值.loc会将整数转换为浮点数,尽管这似乎不适用于较长的选择。但是,即使这样,如果在一系列此类操作中,np.float64也会保留在df.to_csv(file), pd.read_csv(file)操作下。问题似乎出现在混合数据类型给出 Series dtypeobject上,然后导致这些浮点数在写入文件时被视为它们的字符串表示,从而导致精度损失。

因此,在将每个 df 中所需的值提取到元组之前,避免转换为 pandas 对象,

df_summaries = []
columns = ['Start time', 'End time']  # and any other you wanted here
for df in long_list_of_dfs:
# build your tuples of desired df info
summary = pd.DataFrame(df_summaries, columns=columns)

或者为每个构造一个单行 df,以允许按字段正确识别数据类型,并在这些数据类型上使用pd.concat(这比为每个数据类型使用.append要快得多)

df_summaries = []
columns = ['Start time', 'End time']  # and any other you wanted here
for df in long_list_of_dfs:
# build your summary row dataframes of desired info from full-size dataframes
summary = pd.concat(df_summaries)

应该可以解决您的问题。

注意:我无法重现 Edit2 中看到的问题。按照这些步骤对我来说,可以完全精确地恢复浮标。

最新更新