Python - 向数据框添加行的有效方法

Posted

技术标签:

【中文标题】Python - 向数据框添加行的有效方法【英文标题】:Python - Efficient way to add rows to dataframe 【发布时间】:2017-06-12 18:53:36 【问题描述】:

从question 和其他人看来,似乎不建议使用concatappend 来构建熊猫数据框,因为它每次都在重新复制整个数据框。

我的项目涉及每 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 和“设置放大”的写时复制行为的一个特点是,随着 DataFrames 的增大,它会变得越来越慢:

%%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 - 向数据框添加行的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

Python pandas:逐行填充数据框

Pandas 从重采样中检索添加行的索引

C#中DataTable动态添加行和删除行的问题?

向 pyspark 数据框添加行索引(并排添加新列/连接数据框)

检查数据框是不是有任何行的Pandaic方法[重复]

为啥向现有行添加新行时,链接/联结表行的主 ID 会发生变化?