繁体   English   中英

使用 DataFrame / BigQuery 加速 Python 循环

[英]Speed up Python loop with DataFrame / BigQuery

这个循环目前在我以 5ghz (OC) 运行的桌面上花费了将近 3 个小时。 我将如何加速它 go ?

df = pd.DataFrame(columns=['clientId', 'url', 'count'])

idx = 0
for row in rows:
    df.loc[idx] = pd.Series({'clientId': row.clientId, 'url': row.pagePath, 'count': row.count})
    idx += 1

Rows 是存储在 (BigQuery) RowIterator 中的 JSON 数据。

<google.cloud.bigquery.table.RowIterator object at 0x000001ADD93E7B50>
<class 'google.cloud.bigquery.table.RowIterator'>

JSON 数据如下:

Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-us/index.html', 45), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-us/contact.html', 65), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-au/index.html', 64), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-au/products.html', 56), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-us/employees.html', 54), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-us/contact/cookies.html', 44), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-au/careers.html', 91), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-ca/careers.html', 42), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-us/contact.html', 44), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/', 115), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/suppliers', 51), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-us/search.html', 60), {'clientId': 0, 'pagePath': 1, 'count': 2})
Row(('xxxxxxxxxx.xxxxxxxxxx', '/en-au/careers.html', 50), {'clientId': 0, 'pagePath': 1, 'count': 2})

这不是您使用 pandas dataframe 的方式。dataframe 垂直表示数据,这意味着每一列都是引擎盖下的一个系列,它使用固定大小的 numpy 数组(尽管相同数据类型的列的 arrays 与其他列相邻)。

每次你 append 一个新行到 dataframe 时,每一列的数组都会调整大小(即重新分配),这本身就是昂贵的。 您正在为每一行执行此操作,这意味着您对唯一数据类型的每一列进行了n次数组重新分配迭代,这是非常低效的。 此外,您还为每一行创建一个 pd.Series,这会导致更多分配,这在 dataframe 垂直表示数据时没有用。

您可以通过查看列的id来验证这一点

>>> import pandas as pd
>>> df = pd.DataFrame(columns=['clientId', 'url', 'count'])

# Look at the ID of the DataFrame and the columns
>>> id(df)
1494628715776

# These are the IDs of the empty Series for each column
>>> id(df['clientId']), id(df['url']), id(df['count'])
(1494628789264, 1494630670400, 1494630670640)

# Assigning a series at an index that didn't exist before
>>> df.loc[0] = pd.Series({'clientId': 123, 'url': 123, 'count': 100})

# ID of the dataframe remains the same
>>> id(df)
1494628715776

# However, the underlying Series objects are different (newly allocated)
>>> id(df['clientId']), id(df['url']), id(df['count'])
(1494630712656, 1494630712176, 1494630712272)

通过迭代添加新行,您每次迭代都会重新创建新的 Series 对象,因此速度很慢。 这也在.append()方法下的 pandas 文档中发出警告(尽管不推荐使用该参数,但该参数仍然存在): https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.append.html#pandas.882790638195928884

迭代地将行附加到 DataFrame 可能比单个连接的计算密集度更高。 更好的解决方案是将这些行 append 到一个列表,然后将列表与原始 DataFrame 一起连接起来。

在调用pd.DataFrame之前,您最好进行迭代并附加到更适合动态大小操作的数据结构中,例如本机 Python list 但是,对于简单的情况,您可以将生成器传递到pd.DataFrame调用中:

# No need to specify columns since you provided the dictionary with the keys
df = pd.DataFrame({'clientId': row.clientId, 'url': row.pagePath, 'count': row.count} for row in rows)

演示 jupyter notebook 的区别:

def reallocating_way(rows):
    df = pd.DataFrame(columns=['clientId', 'url', 'count'])
    for idx, row in enumerate(rows):
        df.loc[idx] = pd.Series({'clientId': row.clientId, 'url': row.pagePath, 'count': row.count})
    return df

def better_way(rows):
    return pd.DataFrame({'clientId': row.clientId, 'url': row.pagePath, 'count': row.count} for row in rows)

# Making an arbitrary list of 1000 rows
rows = [Row() for _ in range(1000)]

%timeit reallocating_way(rows)
%timeit better_way(rows)

2.45 s ± 118 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.8 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# Making an arbitrary list of 10000 rows
rows = [Row() for _ in range(10000)]

%timeit reallocating_way(rows)
%timeit better_way(rows)

27.3 s ± 1.88 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
12.4 ms ± 142 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

1000 行快 1000 倍以上,10000 行快 2000 倍以上

我在 BigQuery 中遇到了 to_dataframe() 方法。 极快。 将 3 小时缩短为 3 秒。

df = query_job.result().to_dataframe()

google.cloud.bigquery.table.RowIterator

使用 BigQuery 存储 API 将 BigQuery 数据下载到 pandas

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM