高效的方法向数据框中添加行

46

从这个问题和其他问题来看,似乎不推荐使用concatappend来构建Pandas DataFrame,因为每次都要重新复制整个DataFrame。

我的项目涉及每30秒检索少量数据。这可能会持续3天周末,因此有人可以轻松地期望创建一行一个接一行超过8000行。添加行到此DataFrame的最有效方法是什么?


2
如果您每30秒只添加一行,那么它真的需要高效吗? - Stephen Rauch
4
需要使用DataFrame的原因是什么?为什么不直接将其写入文件然后在最后进行转换? - sundance
4
如果您的担忧是因为附加数据需要更长的时间,导致 30 秒的睡眠时间不准确,那么请修复睡眠时间。next_time += 30, time.sleep(next_time-time.time()) - Stephen Rauch
好的,既然我们已经解决了时间问题,按照@burhankhalid下面的答案的思路,将您的数据存储到磁盘上(或数据库中),以便稍后进行处理。对于任何长期数据采集来说,这样更可靠。 - Stephen Rauch
可能是在pandas.DataFrame中添加一行的重复问题。 - fred
显示剩余2条评论
8个回答

83

我使用了这个答案中的 df.loc[i] = [new_data] 建议,但是我的数据行数超过了500,000行,速度非常慢。

虽然给出的答案对于OP的问题是好的,但是我发现当处理大量行时(而不是OP所描述的技巧),使用csvwriter将数据添加到内存中的CSV对象,最后使用pandas.read_csv(csv)生成所需的DataFrame输出更加高效。

from io import BytesIO
from csv import writer 
import pandas as pd

output = BytesIO()
csv_writer = writer(output)

for row in iterable_object:
    csv_writer.writerow(row)

output.seek(0) # we need to get back to the start of the BytesIO
df = pd.read_csv(output)
return df

对于大约 500,000 行数据,这种方式的速度比 df.loc[1] = [data] 方法快了 1000 倍,而且随着行数的增加,速度提升只会越来越明显。

希望这能帮助那些需要高效处理比 OP 更多行数据的人。


2
可以使用内存结构或CSV,而不是将CSV实际写入文件,从而以另一种高效的方式完成操作吗? - matanster
2
太好了!我测试过了,可以确认这个速度快多了。 - Floran Gmehlin
15
注意:对于Python 3需要使用StringIO而不是BytesIO。 - Romi Kuntsman
2
哇,非常感谢您提供这个。比我之前做的要简单得多,速度也更快。它将计算时间从大约2小时缩短到了2分钟! - Aku
2
为什么不直接使用output = [], output.append(row), pd.DataFrame(output)呢? - Xixiaxixi
显示剩余5条评论

38

鉴于原来的回答完全错误,现在我编辑了所选答案。接下来是关于为什么你不应该在扩展时使用"设置"的解释。"设置扩展"实际上比追加还要糟糕。

简而言之使用DataFrame没有高效的方法来完成这个操作,因此如果需要速度,应该使用其他数据结构。请参阅其他答案以获得更好的解决方案。

更多关于扩展设置

您可以使用loc在不存在的索引上就地添加DataFrame行,但这也会执行所有数据的副本操作(请参见此讨论)。以下是它的外观,来自Pandas文档

In [119]: dfi
Out[119]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4

In [120]: dfi.loc[3] = 5

In [121]: dfi
Out[121]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4
3  5  5  5
对于像所描述的用例,使用增量设置实际上比append方法需要更长50%的时间:

使用append()方法,8000行数据需要6.59秒(每行需要0.8毫秒)。

%%timeit df = pd.DataFrame(columns=["A", "B", "C"]); new_row = pd.Series({"A": 4, "B": 4, "C": 4})
for i in range(8000):
    df = df.append(new_row, ignore_index=True)

# 6.59 s ± 53.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

使用.loc(),处理8000行数据只需10秒(每行1.25毫秒)

%%timeit df = pd.DataFrame(columns=["A", "B", "C"]); new_row = pd.Series({"A": 4, "B": 4, "C": 4})
for i in range(8000):
    df.loc[i] = new_row

# 10.2 s ± 148 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

长的DataFrame怎么样?

与数据导向代码中的所有分析一样,您的情况可能有所不同,您应该测试此功能以适应您的使用情况。“追加”和“通过扩展进行设置”的写入复制行为的一个特征是,它会随着大型DataFrame而变得越来越慢:

%%timeit df = pd.DataFrame(columns=["A", "B", "C"]); new_row = pd.Series({"A": 4, "B": 4, "C": 4})
for i in range(16000):
    df.loc[i] = new_row

# 23.7 s ± 286 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

使用这种方法构建一个包含16k行的DataFrame所需的时间比8k行多花费2.3倍。


谢谢,这看起来比我之前使用的要好得多。非常感谢你的帮助! - Jarrod
1
有没有比假定某个索引永远不存在更不像黑客的方法? - matanster
6
当然,后者更快。第一次迭代会添加一个新的行,所有随后的操作都会写入相同索引为3的行。索引必须递增。你还需要使用 df = df.append(df2) 使比较更加公平。 - Moobie
另外,测试使用新行(使用pandas.reindex)进行重新索引,然后用np.array复制新数据可能是一个好主意。 - Nielsou Akbrg

10
Tom Harvey 的响应很好。不过,我想基于 pandas.DataFrame.from_dict 添加一个更简单的答案。

将一行数据添加到列表中,然后将此列表添加到字典中,使用 pd.DataFrame.from_dict(dict) 可以创建一个数据帧,无需迭代。

如果字典的每个值都是一行,则只需使用: pd.DataFrame.from_dict(dictionary,orient='index')

小例子:

# Dictionary containing the data
dic = {'row_1':['some','test','values',78,90],'row_2':['some','test','values',100,589]}

# Creation of the dataframe
df = pd.DataFrame.from_dict(dic,orient='index')
df
          0       1       2      3       4
row_1   some    test    values  78       90
row_2   some    test    values  100     589

即使对于大型词典,它也非常快速。 - Theo
你所说的“大”是什么意思?我需要找到一种在 pandas DataFrame 中添加一百万行数据的替代方法。你认为这样做会更有效率吗? - AleB
我使用它来处理1200万行的数据框,而且它完美地运作。对于大型数据集,字典是完美的选择,因为使用字典添加或删除行的平均时间复杂度为O(1)。 - Theo

5
你需要将问题分为两个部分:
1. 高效地每30秒接收(收集)数据。 2. 在收集到数据之后进行处理。
如果你的数据很重要(即不能承受丢失),请将其发送到队列,然后按批次从队列中读取。
队列提供可靠(保证)的接受方式,确保你的数据不会丢失。
你可以从队列中读取数据并将其存储在数据库中。
现在你的Python应用程序只需从数据库中读取数据,在应用程序合理的时间间隔内进行分析 - 也许你想进行每小时平均值;在这种情况下,你会运行脚本以从数据库中提取数据,并可能将结果写入另一个数据库/表/文件。
最重要的是,将应用程序的收集和分析部分分开。

这是一个很棒的想法!可能有点超出了我目前的技能水平,但这给了我很多好的想法!!我想在把它运行起来后,我会尝试做出类似的东西。谢谢! - Jarrod

2
假设您的数据框按顺序进行索引,您可以执行以下操作:
首先检查下一个索引值以创建新行:
myindex = df.shape[0]+1 

然后使用“at”来写入每个所需的列。原始答案翻译成“最初的回答”。
df.at[myindex,'A']=val1
df.at[myindex,'B']=val2
df.at[myindex,'C']=val3

2

sundance的答案在使用方面可能是正确的,但基准测试结果是错误的。 正如moobie所指出的,在这个例子中索引3已经存在,这使得访问比不存在的索引要快得多。请看这个:

%%timeit
test = pd.DataFrame({"A": [1,2,3], "B": [1,2,3], "C": [1,2,3]})
for i in range(0,1000):
    testrow = pd.DataFrame([0,0,0])
    pd.concat([test[:1], testrow, test[1:]])

每次循环平均需要2.15秒,标准偏差为88毫秒(7次运行,每次1个循环)。

%%timeit
test = pd.DataFrame({"A": [1,2,3], "B": [1,2,3], "C": [1,2,3]})
for i in range(0,1000):
    test2 = pd.DataFrame({'A': 0, 'B': 0, 'C': 0}, index=[i+0.5])
    test.append(test2, ignore_index=False)
test.sort_index().reset_index(drop=True)

每个循环972毫秒±14.4毫秒(7次运行的平均值±标准差,每次1个循环)

%%timeit
test = pd.DataFrame({"A": [1,2,3], "B": [1,2,3], "C": [1,2,3]})
for i in range(0,1000):
    test3 = [0,0,0]
    test.loc[i+0.5] = test3
test.reset_index(drop=True)

每个循环1.13秒±46毫秒(7次运行的平均值±标准偏差,每次1个循环)

当然,这完全是人造的,我承认我没有期望这些结果,但看起来使用不存在的索引.loc.append执行的效果非常相似。就把它放在这里。


2
我从SQL服务器返回了70万行数据。 以上所有方法对我来说都太慢了。 以下方法显著缩短了时间。
from collections import defaultdict
dict1 = defaultdict(list)

for row in results:

   dict1['column_name1'] = row['column_name1']


   dict1['column_name20'] = row['column_name20']

df = pd.DataFrame(dict1)

这就是我所需要的全部。


0

我的同事告诉我要制作一个字典条目清单,然后将完成的清单推入数据框中。与逐个将字典推入数据框相比,这种清单方法是瞬间完成的。

此代码遍历了约54k的记录,并仅查找目标日期时间值之后的记录,然后将所需的值写回到列表,最后再写入df_out:

df_out = pd.DataFrame()
df_len = df.count()
counter = 1
list_out = []
targ_datetime = datetime.datetime.fromisoformat('2021-12-30 00:00:00')
for rec in df.selectExpr("CAST(data as STRING) as data").take(df_len):
  j = jsonx.loads(rec[0])
  NewImage = j['dynamodb']['NewImage']
  NewImage['eventName'] = j['eventName']
  if j.get('dynamodb').get('NewImage').get('UPDATED_AT') != None:
    ts = datetime.datetime.fromisoformat(str(j['dynamodb']['NewImage']['UPDATED_AT']).replace('T', ' ')[0:-5])
  else:
    ts = datetime.datetime.fromtimestamp(j['dynamodb']['ApproximateCreationDateTime']/1000)
  if ts >= targ_datetime:
    #df_out = df_out.append(pd.Series(NewImage.values(), index=NewImage.keys()), ignore_index=True)
    j['dynamodb']['NewImage']['UPDATED_AT'] = ts
    list_out.append(NewImage)
    counter = counter +1
  #if counter > 10: break
df_out = pd.DataFrame(list_out)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接