Python - 向数据框添加行的有效方法
Posted
技术标签:
【中文标题】Python - 向数据框添加行的有效方法【英文标题】:Python - Efficient way to add rows to dataframe 【发布时间】:2017-06-12 18:53:36 【问题描述】:从question 和其他人看来,似乎不建议使用concat
或append
来构建熊猫数据框,因为它每次都在重新复制整个数据框。
我的项目涉及每 30 秒检索一次少量数据。这可能会持续 3 天的周末,因此有人可以很容易地期望一次创建超过 8000 行。向此数据框添加行的最有效方法是什么?
【问题讨论】:
如果你只是每30秒添加一行,真的需要高效吗? 有什么理由需要它是一个DataFrame吗?为什么不直接将其写入文件,然后在最后进行转换? @Stephen Rauch 好吧,我希望我的样本尽可能接近每 30 秒一次。可能是错误的,我正在提取数据,然后将其添加到数据框中,然后使用time.sleep(30)
直到获取下一组数据。我担心的是加载时间会开始扩大每个样本之间的时间。从这个问题link看来,大小为 6000 需要 2.29 秒。如果可能的话,我希望将这个数字保持在最低限度。
如果您担心 30 秒睡眠将不准确,因为附加数据需要更长的时间,然后修复睡眠。 next_time += 30, time.sleep(next_time-time.time())
@Stephen Rauch 哦,这是个好主意!
【参考方案1】:
你需要把问题分成两部分:
-
每 30 秒有效地接受(收集)数据。
在收集数据后对其进行处理。
如果您的数据很重要(也就是说,您不能丢失它)- 将其发送到队列,然后从队列中分批读取。
队列将提供可靠(有保证的)接受,并且您的数据不会丢失。
您可以从队列中读取数据并将其转储到数据库中。
现在,您的 Python 应用程序只需从数据库中读取数据,并以对应用程序有意义的任何时间间隔进行分析 - 也许您想要每小时平均一次;在这种情况下,您将每小时运行一次脚本以从数据库中提取数据,并可能将结果写入另一个数据库/表/文件中。
底线 - 拆分应用程序的收集和分析部分。
【讨论】:
这是个好主意!目前可能有点超出我的技能水平,但这只是给了我很多好主意!!!我想在我启动并运行它之后,我会尝试让这样的事情发生。谢谢!【参考方案2】:在这里编辑选择的答案,因为它完全是错误的。下面解释了为什么您不应该使用带放大的设置。 “设置放大”实际上比追加更糟糕。
这里的 tl;dr 是 没有有效的方法可以使用 DataFrame 执行此操作,因此如果您需要速度,您应该使用其他数据结构。 请参阅其他答案以获得更好的解决方案。
更多关于放大设置
您可以在不存在的索引上使用loc
就地向DataFrame 添加行,但这也会执行所有数据的副本(请参阅this discussion)。这是它的外观,来自Pandas documentation:
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 怎么样?
与所有面向数据的代码中的分析一样,YMMV 并且您应该针对您的用例进行测试。 append
和“设置放大”的写时复制行为的一个特点是,随着 DataFrame
s 的增大,它会变得越来越慢:
%%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 倍。
【讨论】:
谢谢,这看起来比我使用的要好得多。感谢您的帮助! 有什么比假设某些索引永远不存在更不那么骇人听闻的吗? 当然后者更快。第一次迭代添加一个新行,所有后续操作都写入索引为 3 的同一行。索引必须递增。您还需要 df = df.append(df2) 使比较公平。 另外,使用新行(使用 pandas.reindex)测试重新索引,然后使用 np.array 复制新数据可能是个好主意。【参考方案3】:我使用了这个答案的 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 行来说速度提高了 1000 倍,并且随着行数的增加,速度提升只会越来越大(the df.loc[1] = [data]
相对来说会慢很多)
希望这可以帮助那些在处理比 OP 更多的行时需要效率的人。
【讨论】:
能否有效地使用内存结构或 CSV 而不是实际将 CSV 写入文件? @matanster 据我了解,这也是作者自己实际上所说的,这已经在记忆中了。很难打败它。这实际上比附加到列表并转换它更快吗? 太棒了!我进行了测试,可以确认这要快得多。 注意:对于 Python 3 需要use StringIO instead of BytesIO 哇,非常感谢。比我正在做的更容易和更快。它将计算时间从约 2 小时缩短到 2 分钟!【参考方案4】:假设您的数据框已按顺序编入索引:
首先检查下一个索引值是什么以创建新行:
myindex = df.shape[0]+1
然后使用 'at' 写入每个所需的列
df.at[myindex,'A']=val1
df.at[myindex,'B']=val2
df.at[myindex,'C']=val3
【讨论】:
【参考方案5】: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
的表现非常相似。把这个留在这里。
【讨论】:
【参考方案6】: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
【讨论】:
即使是大字典也很快。 大是什么意思?我需要找到一种替代方法来扩大/增长具有一百万行的 pandas DataFrame。你认为这会更有效率吗? 我将它用于 1200 万行的数据框。而且效果很好。字典非常适合大型数据集,因为使用字典时,添加或删除行的平均时间复杂度为 O(1)。【参考方案7】:我从 SQL 服务器返回了 700K 行数据。 以上所有对我来说都花了太长时间。 以下方法显着缩短了时间。
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)
这就是我所需要的。
【讨论】:
【参考方案8】:我的同事告诉我制作一个字典条目列表,然后将完成的列表推送到数据框中。与一次将一个字典推入数据框相比,列表方法是即时的。
这段代码从大约 54k 条记录中剔除,只查找在我的 targ_datetime 值之后的那些,然后将所需的值写回列表,然后再写入 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)
【讨论】:
以上是关于Python - 向数据框添加行的有效方法的主要内容,如果未能解决你的问题,请参考以下文章