Pandas数据处理加速技巧汇总
作者:Mr数据杨 发布时间:2023-08-12 19:02:36
Pandas 处理数据的效率还是很优秀的,相对于大规模的数据集只要掌握好正确的方法,就能让在数据处理时间上节省很多很多的时间。
Pandas 是建立在 NumPy 数组结构之上的,许多操作都是在 C 中执行的,要么通过 NumPy,要么通过 Pandas 自己的 Python 扩展模块库,这些模块用 Cython 编写并编译为 C。理论上来说处理速度应该是很快的。
那么为什么同样一份数据由2个人处理,在设备相同的情况下处理时间会出现天差地别呢?
需要明确的是,这不是关于如何过度优化 Pandas 代码的指南。如果使用得当 Pandas 已经构建为可以快速运行。此外优化和编写干净的代码之间存在很大差异。
这是以 Python 方式使用 Pandas 以充分利用其强大且易于使用的内置功能的指南。
数据准备
此示例的目标是应用分时能源关税来计算一年的能源消耗总成本。 也就是说,在一天中的不同时间,电价会有所不同,因此任务是将每小时消耗的电量乘以消耗该小时的正确价格。
从一个包含两列的 CSV 文件中读取数据,一列用于日期加时间,另一列用于以千瓦时 (kWh) 为单位消耗的电能。
日期时间数据优化
import pandas as pd
df = pd.read_csv('数据科学必备Pandas实操数据处理加速技巧汇总/demand_profile.csv')
df.head()
date_time energy_kwh
0 1/1/13 0:00 0.586
1 1/1/13 1:00 0.580
2 1/1/13 2:00 0.572
3 1/1/13 3:00 0.596
4 1/1/13 4:00 0.592
乍一看这看起来不错,但有一个小问题。 Pandas 和 NumPy 有一个 dtypes(数据类型)的概念。 如果未指定任何参数,则 date_time 将采用 object dtype。
df.dtypes
date_time object
energy_kwh float64
dtype: object
type(df.iat[0, 0])
str
object 不仅是 str 的容器,而且是任何不能完全适合一种数据类型的列的容器。将日期作为字符串处理会既费力又低效(这也会导致内存效率低下)。为了处理时间序列数据,需要将 date_time 列格式化为日期时间对象数组( Timestamp)。
df['date_time'] = pd.to_datetime(df['date_time'])
df['date_time'].dtype
datetime64[ns]
现在有一个名为 df 的 DataFrame,有两列和一个用于引用行的数字索引。
df.head()
date_time energy_kwh
0 2013-01-01 00:00:00 0.586
1 2013-01-01 01:00:00 0.580
2 2013-01-01 02:00:00 0.572
3 2013-01-01 03:00:00 0.596
4 2013-01-01 04:00:00 0.592
使用 Jupyter 自带的 %%time 计时装饰器进行测试。
def convert(df, column_name):
return pd.to_datetime(df[column_name])
%%time
df['date_time'] = convert(df, 'date_time')
Wall time: 663 ms
def convert_with_format(df, column_name):
return pd.to_datetime(df[column_name],format='%d/%m/%y %H:%M')
%%time
df['date_time'] = convert(df, 'date_time')
Wall time: 1.99 ms
处理效率提高将近350倍。如果在处理大规模数据的情况下,处理数据的时间会无限的放大。
数据的简单循环
既然日期和时间格式处理完毕,就可以着手计算电费了。成本因小时而异,因此需要有条件地将成本因素应用于一天中的每个小时。
在此示例中,使用时间成本将定义成三个部分。
data_type = {
# 高峰
"Peak":{"Cents per kWh":28,"Time Range":"17:00 to 24:00"},
# 正常时段
"Shoulder":{"Cents per kWh":20,"Time Range":"7:00 to 17:00"},
# 非高峰
"Off-Peak":{"Cents per kWh":12,"Time Range":"0:00 to 7:00"},
}
如果价格是一天中每小时每千瓦时 28 美分。
df['cost_cents'] = df['energy_kwh'] * 28
date_time energy_kwh cost_cents
0 2013-01-01 00:00:00 0.586 16.408
1 2013-01-01 01:00:00 0.580 16.240
2 2013-01-01 02:00:00 0.572 16.016
3 2013-01-01 03:00:00 0.596 16.688
4 2013-01-01 04:00:00 0.592 16.576
...
但是成本计算取决于一天中的不同时间。这就是你会看到很多人以意想不到的方式使用 Pandas 的地方,通过编写一个循环来进行条件计算。
def apply_tariff(kwh, hour):
"""计算给定小时的电费"""
if 0 <= hour < 7:
rate = 12
elif 7 <= hour < 17:
rate = 20
elif 17 <= hour < 24:
rate = 28
else:
raise ValueError(f'无效时间: {hour}')
return rate * kwh
def apply_tariff(kwh, hour):
"""计算给定小时的电费"""
if 0 <= hour < 7:
rate = 12
elif 7 <= hour < 17:
rate = 20
elif 17 <= hour < 24:
rate = 28
else:
raise ValueError(f'无效时间: {hour}')
return rate * kwh
def apply_tariff_loop(df):
energy_cost_list = []
for i in range(len(df)):
# 循环数据直接修改df
energy_used = df.iloc[i]['energy_kwh']
hour = df.iloc[i]['date_time'].hour
energy_cost = apply_tariff(energy_used, hour)
energy_cost_list.append(energy_cost)
df['cost_cents'] = energy_cost_list
Wall time: 2.59 s
循环 .itertuples() 和 .iterrows() 方法
Pandas 实际上 for i in range(len(df)) 通过引入 DataFrame.itertuples() 和 DataFrame.iterrows() 方法使语法就可能显得多余,这些都是yield一次一行的生成器方法。
.itertuples() 为每一行生成一个命名元组,行的索引值作为元组的第一个元素。 名称元组是来自 Python 集合模块的数据结构,其行为类似于 Python 元组,但具有可通过属性查找访问的字段。
.iterrows() 为 DataFrame 中的每一行生成 (index, Series) 对(元组)。
def apply_tariff_iterrows(df):
energy_cost_list = []
for index, row in df.iterrows():
energy_used = row['energy_kwh']
hour = row['date_time'].hour
energy_cost = apply_tariff(energy_used, hour)
energy_cost_list.append(energy_cost)
df['cost_cents'] = energy_cost_list
%%time
apply_tariff_iterrows(df)
Wall time: 808 ms
速度提高又3倍之多。
.apply() 方法
可以使用 .apply() 方法进一步改进此操作。 Pandas 的 .apply() 方法采用函数(可调用对象)并将它们沿 DataFrame 的轴(所有行或所有列)应用。
lambda 函数将两列数据传递给 apply_tariff()。
def apply_tariff_withapply(df):
df['cost_cents'] = df.apply(
lambda row: apply_tariff(
kwh=row['energy_kwh'],
hour=row['date_time'].hour),
axis=1)
%%time
apply_tariff_withapply(df)
Wall time: 181 ms
.apply() 的语法优势很明显,代码简洁、易读、明确。在这种情况下所用时间大约是该 .iterrows() 方法的4分之一。
.isin() 数据选择
但是如何在 Pandas 中将条件计算应用为矢量化操作呢?一个技巧是根据的条件选择和分组 DataFrame 的部分,然后对每个选定的组应用矢量化操作。
使用 Pandas 的.isin()方法选择行,然后在矢量化操作中应用。在执行此操作之前,如果将 date_time 列设置为 DataFrame 的索引会更方便。
df.set_index('date_time', inplace=True)
def apply_tariff_isin(df):
peak_hours = df.index.hour.isin(range(17, 24))
shoulder_hours = df.index.hour.isin(range(7, 17))
off_peak_hours = df.index.hour.isin(range(0, 7))
df.loc[peak_hours, 'cost_cents'] = df.loc[peak_hours, 'energy_kwh'] * 28
df.loc[shoulder_hours,'cost_cents'] = df.loc[shoulder_hours, 'energy_kwh'] * 20
df.loc[off_peak_hours,'cost_cents'] = df.loc[off_peak_hours, 'energy_kwh'] * 12
%%time
apply_tariff_isin(df)
Wall time: 53.5 ms
其中整个过程方法返回一个布尔列表。
[False, False, False, ..., True, True, True]
.cut() 数据分箱
设置时间切分的列表和对那个计算的函数公式,让操作起来更简单,但是这个对于新手来说代码阅读起来有些困难。
def apply_tariff_cut(df):
cents_per_kwh = pd.cut(x=df.index.hour,
bins=[0, 7, 17, 24],
include_lowest=True,
labels=[12, 20, 28]).astype(int)
df['cost_cents'] = cents_per_kwh * df['energy_kwh']
%%time
apply_tariff_cut(df)
Wall time: 2.99 ms
Numpy 方法处理
Pandas Series 和 DataFrames 是在 NumPy 库之上设计的。这为提供了更大的计算灵活性,因为 Pandas 可以与 NumPy 数组和操作无缝协作。
使用 NumPy 的digitize()函数。它与 Pandas 的相似之处cut()在于数据将被分箱,但这次它将由一个索引数组表示,该数组表示每个小时属于哪个箱。然后将这些索引应用于价格数组。
import numpy as np
def apply_tariff_digitize(df):
prices = np.array([12, 20, 28])
bins = np.digitize(df.index.hour.values, bins=[7, 17, 24])
df['cost_cents'] = prices[bins] * df['energy_kwh'].values
%%time
apply_tariff_digitize(df)
Wall time: 1.99 ms
处理效率比较
对比一下上面几种不同的处理方式的效率吧。
功能 | 运行时间(秒) |
---|---|
apply_tariff_loop() | 2.59 s |
apply_tariff_iterrows() | 808 ms |
apply_tariff_withapply() | 181 ms |
apply_tariff_isin() | 53.5 ms |
apply_tariff_cut() | 2.99 ms |
apply_tariff_digitize() | 1.99 ms |
HDFStore 防止重新处理
通常构建复杂的数据模型时,对数据进行一些预处理会很方便。如果有 10 年的分钟频率用电量数据,即指定了格式参数简单地将日期和时间转换为日期时间也可能需要 20 分钟。只需要这样做一次而不是每次运行模型时都需要进行测试或分析。
可以在这里做的一件非常有用的事情是预处理,然后以处理后的形式存储数据,以便在需要时使用。但是如何才能以正确的格式存储数据而无需再次重新处理呢?如果要保存为 CSV 只会丢失您的日期时间对象,并且在再次访问时必须重新处理它。
Pandas 有一个内置的解决方案使用 HDF5,一种专为存储表格数据数组而设计的高性能存储格式。Pandas 的 HDFStore 类允许将 DataFrame 存储在 HDF5 文件中,以便可以有效地访问它,同时仍保留列类型和其他元数据。dict 是一个类似字典的类,因此可以像对 Python对象一样进行读写。
将预处理的耗电量 DataFrame 存储df在 HDF5 文件中。
data_store = pd.HDFStore('processed_data.h5')
# 将 DataFrame 放入对象中,将键设置为 preprocessed_df
data_store['preprocessed_df'] = df
data_store.close()
从 HDF5 文件访问数据的方法,并保留数据类型。
data_store = pd.HDFStore('processed_data.h5')
preprocessed_df = data_store['preprocessed_df']
data_store.close()
来源:https://blog.csdn.net/qq_20288327/article/details/124244006


猜你喜欢
- PyQt5不规则窗口实现动画效果实例import sysfrom PyQt5.QtCore import *from PyQt5.QtGui
- 前言本篇文章主要讲述了Mac下Redis的安装和使用的经验,并将python如何操作Redis做了简单介绍。1. redis 安装 和启动1
- 大致效果安装方式 IDEA插件官网地址:material theme ui GITHUB地址:material theme ui 下载之后选
- Embedding的近邻搜索是当前图推荐系统非常重要的一种召回方式,通过item2vec、矩阵分解、双塔DNN等方式都能够产出训练好的use
- 目录jQuery的$.ajaxWebpack时代的开始深入了解Promise消灭嵌套await-to-js总结jQuery的$.ajax在开
- 使用input和raw_input都可以读取控制台的输入,但是input和raw_input在处理数字时是有区别的纯数字输入当输入为纯数字时
- declare @tt varchar(20) set @tt = 'monisubbouns' declare @int
- 在Python中使用json的时候,主要也就是使用json模块,json是以一种良好的格式来进行数据的交互,从而在很多时候,可以使用json
- 首先,我们用webpy写一个简单的网站,监听8080端口,返回“Hello, EverET.org”的页面。然后我们使用我们的forward
- 这篇文章主要介绍了如何基于python操作json文件获取内容,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 本文介绍了vue生成随机验证码的示例代码,分享给大家,具体如下:样式自调,最终效果如图:实现效果:点击右边input框会自动切换,如果输入的
- 有时我们会碰到类似下面这样的 unicode 字符串:u'\xe4\xbd\xa0\xe5\xa5\xbd'这明显不是一个正
- python和PHP的难易程度并没有明确的界限,如果是零基础的小白,建议学php会好一些,原因大公司小公司创业公司非互联网公司,都会用到ph
- 前端开发环境多数基于Node.js,好处不多说了。但与使用Visual Studio开发的后端Asp.Net Core项目一起调试,却不是很
- 本文实例讲述了mysql存储过程原理与使用方法。分享给大家供大家参考,具体如下:存储过程包含了一系列可执行的sql语句,存储过程存放于MyS
- 控制结构就是for,while,if-else,if-elif,while…else,在web.py中其实和我们以前学过的一样,操作基本是相
- /* --SQLServer中将字符串首字母设置大写: --作者:jinjazz /csdn --SQLServer2005启用OLEAut
- 使用字符串创建矩阵是一个很实用的功能,之前自己尝试了很多次的小功能使用这个方法就能够简单实现。创建长度为16的字符串,是为了方便能够在各种数
- 1、解决方案mysql是不支持跨库连接的,如果我们实在要连接的话可以用dblink方式。解释:dblink就是我们在创建表的时候连接到我们的
- 应用一:有时候我们想把一个 list 或者 dict 传递给 javascript,处理后显示到网页上,比如要用 js 进行可视化的数据。请