【问题标题】:Python pandas integer precision loss through implicit conversionPython Pandas整数精度丢失通过隐式转换
【发布时间】:2017-10-23 14:21:28
【问题描述】:

我正在处理许多不同的 csv 文件,这些文件以 pandas 数据帧的形式读取,然后从中提取有趣的索引和数据,并将其收集到一个新的数据帧中,我逐行构建然后保存。每一行代表一个文件中的信息。

原始数据帧以毫秒精度的纪元时间为索引。虽然时间是不必要的精确,但我无法改变它。

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

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

>>> 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 循环中构建了 Dataframe,因为我有很多我想为每行添加的数据点。此外,原始数据帧的数量相当多(约 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

【问题讨论】:

  • 你能给summary.info() 可能有一些NaNs 并且这些列被转换为浮动
  • 我在问题中添加了summary.info() 的前十行。如您所见,尽管没有空值,但仍有一些行被转换为浮点数。
  • 为什么不把Start time等列转换成Timestamp,这样就不会有精度损失
  • 如果没有更好的解决方案,我会考虑这个解决方案。不过,我宁愿使用纪元时间。
  • 我的摘要生成器方法有什么问题?而不是s = [df[k].mean() for k in columns],你可以按照你想要的方式总结它而不会出现问题

标签: python pandas


【解决方案1】:

发生这种情况是因为您附加了一个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

新行

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

这个DataFrame可以使用to_csv()导出

【讨论】:

  • 据我所知,一个系列可以有多种数据类型,例如S = pd.Series({'a':10, 'b':2.5, 'c':'s'})。问题不在于数据类型本身,而是在写入文件时截断了很长的数字(这可能是由于 dtype,但我不知道)。我将在问题中添加另一个简短示例以进行澄清。
  • 那么为什么iterrows documentation 会特别提到这一点,我是否能够使用改编后的原始代码重现此行为
  • 而您的示例系列具有 dtype object,因此保留了整数和浮点数,但 pd.Series({'a':10, 'b':2.5, 'c':5.3}) 的 dtype 为 float64
  • 你是对的,那可能是因为那个字符串。我将代码切换为不包含系列,但仍然存在问题。请在 EDIT2 中查看我的新示例,其中数据框中根本没有浮点数,我不使用 Series 或 df.append(),但无论如何所有值都会转换为它。
  • 我似乎忘记了实际连接数据的行。修好了
【解决方案2】:

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

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

当我尝试它时不会丢失,并且与您的摘要相匹配。

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

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

因此,在您从每个 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 中看到的问题。按照这些步骤,我可以完全精确地返回花车。

【讨论】:

  • 这是正确的,但不幸的是,由于数据量很大,我不想多次通过每个输入数据框。精度损失似乎发生在summary = summary.append(new_line) 行中。
  • @Alarik 不是我最好的时刻。调整为仅访问每个 df 一次。
猜你喜欢
  • 1970-01-01
  • 2013-10-22
  • 1970-01-01
  • 2013-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多