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
猜你喜欢
- nginx简单配置php服务(多个)摘要:大部分网站开发语言都要运行在服务器,比如主流的nginx、apache等等,部署服务器环境对于大部
- 对于时间数据,如2018-09-25 09:28:59,有时需要与Unix时间戳进行相互的运算,此时就需要对两种形式进行转换,在Python
- 首先介绍一下什么是桑葚图?桑基图(Sankey diagram),即桑基能量分流图,也叫桑基能量平衡图。它是一种特定类型的流程图,图中延伸的
- shapefile是GIS中非常重要的一种数据类型,在ArcGIS中被称为要素类(Feature Class),主要包括点(point)、线
- 众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串、列表、元组...)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办
- 下面给大家分享Python爬虫后获取重定向url的两种方法,具体内容如下所示;方法(一)# 获得重定向url from urllib imp
- readline()方法从文件中读取一整行。尾部的换行符保持在字符串中。如果大小参数且非负,那么一个最大字节数,包括结尾的换行和
- 我们平日办公时用得最多的软件是Execl、Word或WPS Office等,你的计算机中一定储存着大量的XLS、DOC、WPS文件吧!网页制
- 本文实例讲述了Python自定义scrapy中间模块避免重复采集的方法。分享给大家供大家参考。具体如下:from scrapy import
- 前言format语法格式:str.format()str是指字符串实例对象,常用格式为‘ ’.for
- 本文实例讲述了Python实现的爬虫刷回复功能。分享给大家供大家参考,具体如下:最近闲的无聊,就想着去看看爬虫,顺着爬虫顺利的做到了模拟登录
- 本文实例讲述了Python 私有化操作。分享给大家供大家参考,具体如下:私有化xx: 公有变量_x: 单前置下划线,私有化属性或方法,fro
- 这篇论坛文章着重介绍了Access数据库出现0x80004005问题的解决方法,更多内容请参考下文:项目做了三个月了,终于也差不多完成了,昨
- 这篇文章主要介绍了python垃圾回收机制(GC)原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 我们先以一个最简单的实例来了解模拟登录后页面的抓取过程,其原理在于模拟登录后 Cookies 的维护。1. 本节目标本节将讲解以 GitHu
- 很早就听说韩国网站的设计师们很会利用空间,来创造更多的信息承载量.最近浏览了几个韩国SHOPPING网站果不其然,就拿小小的广告轮播来说,非
- python中的集合什么是集合?集合是一个无序的不重复元素序列常用来对两个列表进行交并差的处理集合与列表一样,支持所有数据类型集合与列表的区
- 现在的高手真是越来越多,我刚发现一个版主兄竟然在不支持数据库的ISP免费主页上使用数据库,套用QQ聊天的一句话就是:Faint!明明人家IS
- 项目场景:使用Anaconda Prompt创建虚拟环境问题描述保存虚拟环境的默认地址是C盘,而我想将下载的虚拟环境保存到我自定义的位置。解
- 新建图像文件后选Channels面板,新建Alpha1通道; 做压