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

时间:2017-05-23 14:45:43

标签: python pandas

我正在处理大量不同的csv文件作为pandas数据帧读入,然后从中提取有趣的indizes和数据并将其收集到一个新的数据帧中,我逐行构建然后保存。每行代表一个文件中的信息。

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

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

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

>>> 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。我希望没有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

2 个答案:

答案 0 :(得分:1)

我还没有找到你精确度下降的地方,但是

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

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

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

看起来选择.loc的单个值会将整数转换为浮点数,但这似乎并不适用于较长的选择。尽管如此,如果在一系列此类操作中,np.float64仍保留在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中出现的问题。按照这些步骤,我可以完全精确地恢复浮动。

答案 1 :(得分: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
  

NEW_LINE

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

所以转换发生在追加

之前

摘要生成器

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

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