从这个问题和其他问题来看,似乎不推荐使用concat
或append
来构建Pandas DataFrame,因为每次都要重新复制整个DataFrame。
我的项目涉及每30秒检索少量数据。这可能会持续3天周末,因此有人可以轻松地期望创建一行一个接一行超过8000行。添加行到此DataFrame的最有效方法是什么?
我使用了这个答案中的 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 更多行数据的人。
output = []
, output.append(row)
, pd.DataFrame(output)
呢? - Xixiaxixi鉴于原来的回答完全错误,现在我编辑了所选答案。接下来是关于为什么你不应该在扩展时使用"设置"的解释。"设置扩展"实际上比追加还要糟糕。
简而言之,使用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
而变得越来越慢:
%%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倍。
将一行数据添加到列表中,然后将此列表添加到字典中,使用 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
myindex = df.shape[0]+1
df.at[myindex,'A']=val1
df.at[myindex,'B']=val2
df.at[myindex,'C']=val3
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
执行的效果非常相似。就把它放在这里。
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)
这就是我所需要的全部。
我的同事告诉我要制作一个字典条目清单,然后将完成的清单推入数据框中。与逐个将字典推入数据框相比,这种清单方法是瞬间完成的。
此代码遍历了约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)
next_time += 30, time.sleep(next_time-time.time())
。 - Stephen Rauch