迭代 Pandas DataFrame 并插入行的最快方法

Posted

技术标签:

【中文标题】迭代 Pandas DataFrame 并插入行的最快方法【英文标题】:Fastest way to iterate over Pandas DataFrame and insert a Row 【发布时间】:2019-04-10 04:56:38 【问题描述】:

我正在构建一个工具,以帮助每周自动审查来自多个实验室设置的数据。每天都会生成一个制表符分隔的文本文件。每行代表每 2 秒获取的数据,因此有 43200 行和许多列(每个文件为 75mb)

我正在使用 pandas.readcsv 加载七个文本文件,并且只将我需要的三列提取到 pandas 数据框中。这比我想要的慢,但可以接受。然后,我使用 Plotly 离线绘制数据以查看交互式绘图。这是一项设置为每周运行一次的计划任务。

数据是根据日期和时间绘制的。通常,测试设置会暂时离线,并且数据中会出现空白。不幸的是,当绘制此图时,所有数据都通过线连接,即使测试离线数小时或数天。

防止这种情况的唯一方法是在两个日期之间插入一行日期,其中包含实际数据和所有缺失数据的 NaN。我已经很容易地为丢失的数据文件实现了这一点,但是我想将这一点概括为大于某个时间段的数据间隙。我想出了一个似乎可行但确实很慢的解决方案:

# alldata is a pandas dataframe with 302,000 rows and 4 columns
# one datetime column and three float32 columns

alldata_gaps  = pandas.DataFrame() #new dataframe with gaps in it

#iterate over all rows. If the datetime difference between 
#two consecutive rows is more than one minute, insert a gap row.

for i in range(0, len(alldata)):
    alldata_gaps = alldata_gaps.append(alldata.iloc[i])
    if alldata.iloc[i+1, 0]-alldata.iloc[i,0] > datetime.timedelta(minutes=1):
        Series = pandas.Series('datetime' : alldata.iloc[i,0]
        +datetime.timedelta(seconds=3))
        alldata_gaps = alldata_gaps.append(Series)
        print(Series)

有没有人建议我如何加快此操作,以免花费如此令人讨厌的长时间?

Here's a dropbox link to an example data file with only 100 lines

Here's a link to my current script without adding the gap rows

【问题讨论】:

您能否提供一个简短示例来说明您的数据是什么样的,以便其他人可以为您的数据制定解决方案? DFs 不可行扩展:追加一行需要线性时间和空间。因此,如果您在循环中追加 n 行,则循环会花费您 O(n^2) 的时间,这会迅速爆发。 【参考方案1】:

几乎可以肯定你的瓶颈来自pd.DataFrame.append

alldata_gaps = alldata_gaps.append(alldata.iloc[i])
alldata_gaps = alldata_gaps.append(Series)

顺便说一句,您将变量命名为与 Pandas 对象 pd.Series 相同的名称令人困惑。避免这种歧义是一种很好的做法。

一个更有效的解决方案是:

    确定出现间隔的时间。 使用这些时间 + 3 秒的数据创建一个数据帧。 附加到现有数据框并按时间排序。

让我们用一个示例数据框来测试一下:

# example dataframe setup
df = pd.DataFrame('Date': ['00:10:15', '00:15:20', '00:15:40', '00:16:50', '00:17:55',
                            '00:19:00', '00:19:10', '00:19:15', '00:19:55', '00:20:58'],
                   'Value': list(range(10)))

df['Date'] = pd.to_datetime('2018-11-06-' + df['Date'])

# find gaps greater than 1 minute
bools = (df['Date'].diff().dt.seconds > 60).shift(-1).fillna(False)
idx = bools[bools].index
# Int64Index([0, 2, 3, 4, 8], dtype='int64')

# construct dataframe to append
df_extra = df.loc[idx].copy().assign(Value=np.nan)

# add 3 seconds
df_extra['Date'] = df_extra['Date'] + pd.to_timedelta('3 seconds')

# append to original
res = df.append(df_extra).sort_values('Date')

结果:

print(res)

                 Date  Value
0 2018-11-06 00:10:15    0.0
0 2018-11-06 00:10:18    NaN
1 2018-11-06 00:15:20    1.0
2 2018-11-06 00:15:40    2.0
2 2018-11-06 00:15:43    NaN
3 2018-11-06 00:16:50    3.0
3 2018-11-06 00:16:53    NaN
4 2018-11-06 00:17:55    4.0
4 2018-11-06 00:17:58    NaN
5 2018-11-06 00:19:00    5.0
6 2018-11-06 00:19:10    6.0
7 2018-11-06 00:19:15    7.0
8 2018-11-06 00:19:55    8.0
8 2018-11-06 00:19:58    NaN
9 2018-11-06 00:20:58    9.0

【讨论】:

【参考方案2】:

我的总体想法与 jpp 的回答相同:您应该只识别感兴趣的行并使用它们,而不是迭代数据框(这对于您拥有的数据量来说很慢)。主要区别是 1) 将多列转换为 NA 和 2) 将 NA 行时间戳调整为周围时间的一半

我在整个过程中都添加了解释作为 cmets...

# after you read in your data, make sure the time column is actually a datetime
df['datetime'] = pd.to_datetime(df['datetime'])

# calculate the (time) difference between a row and the previous row
df['time_diff'] = df['datetime'].diff()

# create a subset of your df where the time difference is greater than
# some threshold. This will be a dataframe of your empty/NA rows.
# I've set a 2 second threshold here because of the sample data you provided, 
# but could be any number of seconds
empty = df[df['time_diff'].dt.total_seconds() > 2].copy()

# calculate the correct timestamp for the NA rows (halfway and evenly spaced)
empty['datetime'] = empty['datetime'] - (empty['time_diff'].shift(-1) / 2)

# set all the columns to NA apart from the datetime column
empty.loc[:, ~empty.columns.isin(['datetime'])] = np.nan

# append this NA/empty dataframe to your original data, and sort by time
df = df.append(empty, ignore_index=True)
df = df.sort_values('datetime').reset_index(drop=True)

# optionally, remove the time_diff column we created at the beginning
df.drop('time_diff', inplace=True, axis=1)

这会给你这样的东西:

【讨论】:

不错的编辑 +1 :)。我要做的唯一更改是您不需要显式添加系列df['time_diff'] 并在以后删除它。您可以将其存储在变量中,即time_diff = df['datetime'].diff(),并使用布尔系列进行比较/索引。 我试过这种方式,但我无法让它完全发挥作用。代码被准确地复制和粘贴。生成timediff 可以正常工作并产生正确的时差,但是当我打印结果系列时,empty['datetime'] = empty['datetime'] -(empty['time_diff'].shift(-1) / 2) 行会出于某种原因产生 NaT。 shift如何知道周期是什么? 没关系,我想通了。 (empty['time_diff'].shift(-1) / 2) 只需更改为 (empty['time_diff'] / 2) 我测试了它,它工作得很好,将 NaN 线放在相邻数字之间的中间。谢谢您的帮助。我唯一的其他经验是使用 C,似乎使用 python 和它的库在概念上比我想象的更不同!看来我已经学会了。

以上是关于迭代 Pandas DataFrame 并插入行的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

Pandas:在组复杂问题中使用条件迭代和插入列

pandas筛选dataframe数据:获取dataframe的第二行到第N行的数据

pandas将列表list插入到dataframe的单元格中pandas使用read_csv函数读取文件并设置保留数值的前置0( leading zeroes)

在Pandas Dataframe中遍历行的不同方法

pandas遍历dataframe的行:迭代遍历dataframe的数据行iterrows函数itertuple函数

pandas筛选dataframe数据:获取dataframe的第N行到第M行的前T列数据列的内容