【问题标题】:Python: Faster way to insert rows into a DataFrame at specific locations?Python:在特定位置将行插入 DataFrame 的更快方法?
【发布时间】:2018-07-29 19:01:47
【问题描述】:

我有一个大约 40,000 行的 DataFrame。 DataFrame 大致如下:

             Unix Time                           UTC  Val. 1  Val. 2  Val. 3
1    1518544176.927486    2018-02-13 17:49:36.927486    5.00    0.25    2.00
2    1518544176.929897    2018-02-13 17:49:36.929897    4.50    1.00    3.00
3    1518544176.932310    2018-02-13 17:49:36.932310    4.00    0.75    1.75
...

第 0、2-4 列是类型 <class 'numpy.float64'>。第 1 列是类型 <class 'pandas._libs.tslib.Timestamp'>。当绘制任何数据列与时间的关系时,我们会看到一个波形。但是,收购中偶尔会出现中断。例如,我们可能有:

               Unix Time                           UTC  Val. 1  Val. 2  Val. 3
576    1518544181.755085    2018-02-13 17:49:41.755085    0.10    0.01    0.93
577    1518544182.041129    2018-02-13 17:49:42.041129    0.11    0.02    0.95
...

如您所见,读数 576 和 577 之间存在约 0.3 秒的差距。问题在于,在绘制数据时,matplotlib 会连接点,即使没有数据。这个“问题”的解决方案已经在 Stack Overflow 和网上的其他问题中得到解决,虽然我不喜欢......好吧,任何一个,最好的选择似乎是将 NaN 插入数据缺口。由于 matplotlib 不绘制 NaN,这是一种偷偷摸摸的方式来欺骗它,让你的情节更真实。

为此,我首先找到前两个读数之间的时间差(这是安全的),然后使用该值的两倍作为“是否存在间隙?”的指标。然后我遍历 DataFrame,检查差距。找到一个后,我在数据列中创建了一个临时的 NaN 行,并在时间列的采集间隙中间创建了时间值。然后,我修改了一个由旧 DataFrame 和这一行组成的新 DataFrame。可以在这里看到:

df2 = df.copy()
for i, row in df.iterrows():
    # The following code checks the delta-t of all timestamp pairs.
    # We have i > 0 because it can't activate on the first entry.
    if i > 0:
        delta_t_unix = row['Unix Time'] - prev_timestamp_unix
        delta_t_utc = row['UTC'] - prev_timestamp_utc
        # If delta_t_unix > the allowed data gap, add new timestamps and NaNs.
        if delta_t_unix > allowed_gap:
            time_unix = row['Unix Time'] - (delta_t_unix / 2.0)
            time_utc = row['UTC'] - (delta_t_utc / 2.0)
            val1 = np.nan
            val2 = np.nan
            val3 = np.nan
            new_row = pd.DataFrame({'Unix Time': time_unix, 'UTC': time_utc,
                                    'Val. 1': val1, 'Val. 2': val2,
                                    'Val. 3': val3}, index = [i])
            df2 = pd.concat([df2.ix[:i-1], new_row,
                            df2.ix[i:]]).reset_index(drop = True)
    # Set the previous timestamp for use in the beginning of the loop.
    prev_timestamp_unix = row[timestamp_unix]
    prev_timestamp_utc = row[timestamp_utc]
# Make the final DataFrame with the completed lists.
df2 = df2[['Unix Time', 'UTC', 'Val. 1', 'Val. 2', 'Val. 3']]

由于this question,这目前需要大约 4.5 秒(它过去需要大约 6.5 秒,因为我愚蠢地迭代并创建每列的新列表,然后从中创建一个新的 DataFrame)。但是,这仍然比我预期或喜欢的要慢得多。有人对如何加快速度有任何想法吗?我对 Pandas 和 DataFrames 还是很陌生,所以我相信这可能会更好。谢谢!

编辑:值得一提的是,如果我删除 datetime 列,它会将时间分成两半(但不幸的是,我无法在实践中删除它)。

【问题讨论】:

  • 其实,我很确定 pandas 有一个内置函数可以满足你的需求,我正在检查它,我会为你发布它
  • 没有找到任何可以产生您所构建的东西的东西。我实际上错误地认为重新采样可以。
  • 我已经更正了帖子中的一些错误,现在应该可以使用了。如果您能分享该代码运行多长时间,我将不胜感激。

标签: python python-3.x pandas dataframe


【解决方案1】:

可以加快速度的东西:

  1. 使用df.itertuples() 代替df.iterrows() 可能会在一定程度上缩短执行时间。

如果您能发布改进,我将不胜感激,但根据我的经验,我测试的案例有很大的不同(循环内更简单的指令改进了 10 倍)。

  1. 不要使用pd.concat 将行放在一起,而是创建一个元组列表,然后仅在从该列表循环之后生成一个DataFrame。

    for i, unix_time, utc_time, val1, val2, val3 in df.itertuples():
    
        list_of_values = []
        if i > 0:
            delta_t_unix = unix_time - prev_timestamp_unix
            delta_t_utc = utc_time - prev_timestamp_utc
    
            if delta_t_unix > allowed_gap:
                new_time_unix = unix_time - (delta_t_unix / 2.0)
                new_time_utc = utc_time - (delta_t_utc / 2.0)
                list_of_values.append((new_time_unix, new_time_utc, np.nan, np.nan, np.nan))
    
        # Set the previous timestamp for use in the beginning of the loop.
        prev_timestamp_unix = unix_time 
        prev_timestamp_utc = utc_time 
    
        list_of_values.append(((unix_time , utc_time, val1, val2, val3))
    
    df2 = pd.DataFrame(list_of_values, columns=['Unix Time', 'UTC', 'Val. 1', 'Val. 2', 'Val. 3'])
    

这可能会大大加快速度。

【讨论】:

  • 我从没听说过itertupes。谢谢!我现在正在运行测试。
  • @erekalper,它缺少这一行list_of_values.append(((new_time_unix, new_time_utc, val1, val2, val3)) 来填充新数据帧上的旧值
  • 是的,我注意到了:)。感谢编辑! (从技术上讲,新附加列表中的值应该是 unix_timeutc_time,但我知道你的意思。)
  • @erekalper,我认为与list 相比,使用queuedeque 可能会加快速度,但由于数据集并不庞大,我认为这将是一个非常小的改进这可能不值得实施。
  • 您的方法 (1) 将其降至 ~1 s,并且 (2) 将其降至 ~0.75 s。这非常有用,我将来肯定会使用 itertuples!
【解决方案2】:

您可以使用以下方法重新采样到 2.4 毫秒:

df['utc_time'] = pd.to_datetime(df['utc_time'])
df.set_index(df['utc_time'])[['val1','val2','val3']].resample('2.4ms', loffset='1.2ms').mean().reset_index()

【讨论】:

  • 这不是一个坏主意,但不幸的是我不认为重采样是我正在寻找的(我认为,除非我误解了它的作用)。我希望能够看到数据的最后一点,包括差距。
  • 如果您将不规则采样的波形重新采样到最小的时间步长,在您显示的数据中似乎是 2.4 毫秒,您不会丢失任何数据。
  • 最小时间步长会随着数据集的变化而变化,并且每个数据集中也会有一些抖动。当然,这很容易解释,但基本点是我实际上需要可视化数据中的差距,这种方法可以消除(至少,我尝试了您发布的内容并消除了差距)。
  • 别担心,我会尽力提供帮助。你在使用 IDE 吗?如果你运行这两行,控制台中会显示什么?你只是得到输入回来?如果要保存结果,则必须在第二行前面添加df1=
  • ...也就是说,它比我们上面所做的要快得多(此时只有几分之一秒,但仍然如此)。我暂时将@joaoavf 作为答案,因为它完美地保留了原始数据,但我肯定会对此进行探索。感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-25
  • 1970-01-01
相关资源
最近更新 更多