pandas如何筛选某⼀⾏包含字符串_Python中的神器
Pandas,但是有⼈说Pand。。。
如果你从事⼤数据⼯作,⽤Python的Pandas库时会发现很多惊喜。Pandas在数据科学和分析领域扮演越来越重要的⾓⾊,尤其是对于从Excel和VBA转向Python的⽤户。
所以,对于数据科学家,数据分析师,数据⼯程师,Pandas是什么呢?Pandas⽂档⾥的对它的介绍是:“快速、灵活、和易于理解的数据结构,以此让处理关系型数据和带有标签的数据时更简单直观。”
快速、灵活、简单和直观,这些都是很好的特性。当你构建复杂的数据模型时,不需要再花⼤量的开发时间在等待数据处理的任务上了。这样可以将更多的精⼒集中去理解数据。
但是,有⼈说Pandas慢…
第⼀次使⽤Pandas时,有⼈评论说:Pandas是很棒的解析数据的⼯具,但是Pandas太慢了,⽆法⽤于统计建模。第⼀次使⽤的时候,确实如此,真的慢。
但是,Pandas是建⽴在NumPy数组结构之上的。所以它的很多操作通过NumPy或者Pandas⾃带的扩展模块编写,这些模块⽤Cython编写并编译到C,并且在C上执⾏。因此,Pandas不也应该很快的吗?
事实上,使⽤姿势正确的话,Pandas确实很快。
在使⽤Pandas时,使⽤纯“python”式代码并不是最效率的选择。和NumPy⼀样,Pandas专为向量化操作⽽设计,它可在⼀次扫描中完成对整列或者数据集的操作。⽽单独处理每个单元格或某⼀⾏这种遍历的⾏为,应该作为备⽤选择。
本教程
先说明下,本教程不是引导如何过度优化Pandas代码。因为Pandas在正确的使⽤下已经很快了。此外,优化代码和编写清晰的代码之间的差异是巨⼤的。
这是⼀篇关于“如何充分利⽤Pandas内置的强⼤且易于上⼿的特性”的指引。此外,你将学习到⼀些实⽤的节省时间的技巧。在这篇教程中,你将学习到:
· 使⽤datetime时间序列数据的优势
· 处理批量计算更效率的⽅法
· 利⽤HDFStore节省时间
在本⽂中,耗电量时间序列数据将被⽤于演⽰本主题。加载数据后,我们将逐步了解更有效率的⽅法取得最终结果。对于Pandas⽤户⽽⾔,会有多种⽅法预处理数据。但是这不意味着所有⽅法都适⽤于更⼤、更复杂的数据集。
任务
本例使⽤能源消耗的时间序列数据计算⼀年能源的总成本。由于不同时间段的电价不同,因此需要将各时段的耗电量乘上对应时段的电价。
从CSV⽂件中可以读取到两列数据:⽇期时间和电⼒消耗(千⽡)
每⾏数据中都包含每⼩时耗电量数据,因此整年会产⽣8760(356×24)⾏数据。每⾏的⼩时数据表⽰计算的开始时间,因此1/1/13 0:00的数据指1⽉1号第1个⼩时的耗电量数据。
⽤Datetime类节省时间
⾸先⽤Pandas的⼀个I/O函数读取CSV⽂件:
>>> import pandas as pd
>>> pd.__version__
'0.23.1'
>>> df = pd.read_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/1
3 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:
>>> df.dtypes
date_time object
energy_kwh float64
dtype: object
>>> type(df.iat[0, 0])
str
默认类object不仅是str类的容器,⽽且不能齐整的适⽤于某⼀种数据类型。字符串str类型的⽇期在数据处理中是⾮常低效的,同时内存效率也是低下的。python怎么读取excel某一列
为了处理时间序列数据,需要将date_time列格式化为datetime类的数组,Pandas 称这种数据类型为时间戳Timestamp。⽤Pandas进⾏格式化相当简单:
>>> df['date_time'] = pd.to_datetime(df['date_time'])
>>> df['date_time'].dtype
datetime64[ns]
⾄此,新的df和CSV file内容基本⼀样。它有两列和⼀个索引。
>>> 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
上述代码简单且易懂,但是有执⾏速度如何呢?这⾥我们使⽤了timing装饰器,这⾥将装饰器称为@timeit。这个装饰器模仿了Python标准库中的peat() ⽅法,但是它可以返回函数的结果,并且打印多次重复调试的平均运⾏时间。Python的peat() 只返回调试时间结果,但不返回函数结果。
将装饰器@timeit放在函数上⽅,每次运⾏函数时可以同时打印该函数的运⾏时间。
>>> @timeit(repeat=3, number=10)
... def convert(df, column_name):
.
.. _datetime(df[column_name])
>>> # Read in again so that we have `object` dtype to start
>>> df['date_time'] = convert(df, 'date_time')
Best of 3 trials with 10 function calls per trial:
Function `convert` ran in average of 1.610 seconds.
看结果如何?处理8760⾏数据耗时1.6秒。这似乎没啥⽑病。但是当处理更⼤的数据集时,⽐如计算更⾼频的电费数据,给出每分钟的电费数据去计算⼀整年的总成本。数据量会⽐现在多60倍,这意味着你需要⼤约90秒去等待输出的结果。这就有点忍不了了。
实际上,作者⼯作中需要分析330个站点过去10年的每⼩时电⼒数据。按上边的⽅法,需要88分钟完成时间列的格式化转换。
有更快的⽅法吗?⼀般来说,Pandas可以更快的转换你的数据。在本例中,使⽤格式参数将csv⽂件中特定的时间格式传⼊Pandas的
to_datetime中,可以⼤幅的提升处理效率。
>>> @timeit(repeat=3, number=100)
>>> def convert_with_format(df, column_name):
... _datetime(df[column_name],
... format='%d/%m/%y %H:%M')
Best of 3 trials with 100 function calls per trial:
Function `convert_with_format` ran in average of 0.032 seconds.
新的结果如何?0.032秒,速度提升了50倍!所以之前330站点的数据处理时间节省了86分钟。
⼀个需要注意的细节是CSV中的时间格式不是ISO 8601格式:YYYY-mm-dd HH:MM。如果没有指定格式,Pandas将使⽤dateuil包将每个字符串格式的⽇期格式化。相反,如果原始的时间格式已经是ISO 8601格式了,Pandas可以快速的解析⽇期。
遍历
⽇期时间已经完成格式化,现在准备开始计算电费了。由于每个时段的电价不同,因此需要将对应的电价映射到各个时段。此例中,电价收费标准如下:
如果电价全天统⼀价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式的⽅式:⽤遍历去完成这类计算。
在本⽂中,将从最基础的解决⽅案开始介绍,并逐步提供充分利⽤Pandas性能优势的Python式解决⽅案。
但是对于Pandas库来说,什么是Python式⽅案?这⾥是指相⽐其他友好性较差的语⾔如C++或者Java,它们已经习惯了“运⽤遍历”去编程。
如果不熟悉Pandas,⼤多数⼈会像以前⼀样使⽤继续遍历⽅法。这⾥继续使⽤@timeit装饰器来看看这种⽅法的效率。
⾸先,创建⼀个不同时段电价的函数:
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'Invalid hour: {hour}')
return rate * kwh
如下代码就是⼀种常见的遍历模式:
>>> # 注意:不要尝试该函数!
>>> @timeit(repeat=3, number=100)
... def apply_tariff_loop(df):
... """⽤遍历计算成本,将结果并⼊到df中"""
... energy_cost_list = []
... for i in range(len(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
...
>>> apply_tariff_loop(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_loop` ran in average of 3.152 seconds.
对于没有⽤过Pandas的Python⽤户来说,这种遍历很正常:对于每个x,再给定条件y下,输出z。
但是这种遍历很笨重。可以将上述例⼦视为Pandas⽤法的“反⾯案例”,原因如下⼏个。
⾸先,它需要初始化⼀个列表⽤于存储输出结果。
其次,它⽤了隐晦难懂的类range(0, len(df))去做循环,接着在应⽤apply_tariff()函数后,还必须将结果增加到列表中⽤于⽣成新的DataFrame列。
最后,它还使⽤链式索引df.iloc[i]['date_time'],这可能会⽣产出很多bugs。
这种遍历⽅式最⼤的问题在于计算的时间成本。对于8760⾏数据,花了3秒钟完成遍历。下⾯,来看看⼀些基于Pandas数据结构的迭代⽅案。
⽤.itertuples()和.iterrow()遍历
还有其他办法吗?
Pandas实际上通过引⼊DataFrame.itertuples()和DataFrame.iterrows()⽅法使得for i in range(len(df))语法冗余。这两种都是产⽣⼀次⼀⾏的⽣成器⽅法。
.itertuples()为每⾏⽣成⼀个nametuple类,⾏的索引值作为nametuple类的第⼀个元素。nametuple是来⾃Python的collections模块的数据结构,该结构和Python中的元组类似,但是可以通过属性查可访问字段。
.iterrows()为DataFrame的每⾏⽣成⼀组由索引和序列组成的元组。
与.iterrows()相⽐,.itertuples()运⾏速度会更快⼀些。本例中使⽤了.iterrows()⽅法,因为很多读者很可能没有⽤过nametuple。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论