Pandas是一个基于Python编程语言构建的开源数据分析工具,它以其易用性和灵活性而广受欢迎。使用Pandas已经有一段时间了,它总是以新特性、快捷方式和多种完成任务的方法给带来惊喜。然而,发现遵循一些学到的约定随着时间的推移是有益的。发现的一些最有用的特性包括'apply()'和'lambda()'。当遇到创建新列或过滤器的复杂逻辑时,就会转向apply和lambda。这种情况经常发生,当一个公司向提出特殊请求时。本文的目标是展示apply和lambda的强大之处。将使用过去10年IMDB上1000部热门电影的数据集。也可以在Kaggle上跟随操作。
import numpy as np # 线性代数
import pandas as pd
# pandas默认设置
pd.options.display.max_columns = 500
pd.options.display.max_rows = 500
import os
首先,需要读取数据。这里使用Pandas的read_csv函数来读取IMDB电影数据。
df = pd.read_csv("IMDB-Movie-Data.csv")
print(df.head()) # 打印前五行数据
接下来,可以重命名一些列,以便于后续的操作。
df.rename(columns={'Revenue (Millions)':'Rev_M','Runtime (Minutes)':'Runtime_min'},inplace=True)
创建新列有多种方法,如果需要一个列是其他列的和或差,可以使用基本的算术运算。例如,可以使用IMDB评分和标准化Metascore来计算平均评分。
df['AvgRating'] = (df['Rating'] + df['Metascore']/10)/2
有时候,可能需要根据一些复杂的逻辑来创建新列。例如,想要根据多个标准创建一个自定义电影评分。假设想要增加惊悚片的IMDB评分,但只有当IMDB评分小于或等于10时。同样,如果电影是喜剧片,想要从排名中减去一分。如何实现这一点呢?在这种情况下,通常会使用apply/lambda。首先,将展示想要实现的目标。
def custom_rating(genre,rating):
if 'Thriller' in genre:
return min(10,rating+1)
elif 'Comedy' in genre:
return max(0,rating-1)
else:
return rating
df['CustomRating'] = df.apply(lambda x: custom_rating(x['Genre'],x['Rating']),axis=1)
一般结构是:创建一个函数,它接受想要实验的列值,并创建逻辑。在这个案例中,只使用了genre和rating列。可以使用apply函数与lambda和axis=1。一般语法是:df.apply(lambda x: function (x[‘col1’],x[‘col2’]),axis=1)。因为只需要关心自定义函数,应该能够使用apply/lambda设计几乎任何逻辑。
使用Pandas进行数据框的过滤和子集选择非常简单。可以使用正常的操作符和&、|操作符来过滤和子集数据框。
# 单条件:所有评分大于8的电影数据框
df_gt_8 = df[df['Rating']>8]
df_gt_8.head()
# 多条件:AND - 所有评分大于8且票数超过100000的电影
And_df = df[(df['Rating']>8) & (df['Votes']>100000)]
And_df.head()
# 多条件:OR - 所有评分大于8或Metascore超过90的电影
Or_df = df[(df['Rating']>8) | (df['Metascore']>80)]
Or_df.head()
# 多条件:NOT - 排除所有评分大于8或Metascore超过90的电影
Not_df = df[~((df['Rating']>8) | (df['Metascore']>80))]
Not_df.head()
这一切都非常简单。然而,在某些时候可能需要复杂的过滤技术。
假设想要找到电影标题中单词数量大于或等于四个的行。如果负责这个操作,会怎么做?如果尝试以下操作,会得到一个错误。没有像使用series分割那样简单的方法。
# 单条件:所有评分大于8的电影数据框
df_gt_8 = df[df['Rating']>8]
# 多条件:AND - 所有评分大于8且票数超过100000的电影
And_df = df[(df['Rating']>8) & (df['Votes']>100000)]
# 多条件:OR - 所有评分大于8或Metascore超过90的电影
Or_df = df[(df['Rating']>8) | (df['Metascore']>80)]
# 多条件:NOT - 排除所有评分大于8或Metascore超过90的电影
Not_df = df[~((df['Rating']>8) | (df['Metascore']>80))]
new_df = df[len(df['Title'].split(" "))>=4]
一种方法是使用apply创建一个包含标题中单词数量的新列,然后根据该列进行过滤。
# 创建一个新列
df['num_words_title'] = df.apply(lambda x : len(x['Title'].split(" ")),axis=1)
# 根据新列进行简单过滤
new_df = df[df['num_words_title']>=4]
new_df.head()
只要不需要构建大量列,这是一个完全合适的情况。然而,更喜欢以下方法:
new_df = df[df.apply(lambda x : len(x['Title'].split(" "))>=4,axis=1)]
new_df.head()
apply函数返回一个布尔值,可以用来过滤,这就是在这里所做的。现在知道了,只需要创建一个布尔列就可以进行过滤,可以在'apply()'语句中使用任何函数/逻辑来创建任何复杂的逻辑。
也使用apply来改变列类型,因为不想记住语法,而且它允许执行更多复杂的事情。在Pandas中,改变列类型的标准语法是astype。假设数据中有一个价格列,格式为str。这是可以做的事情:
df['Price'] = newDf['Price'].astype('int')
然而,这并不总是成功的。可能会收到以下消息:ValueError: '13,000'是一个不正确的long()字面量,基数为10。也就是说,带有“,”的字符串不能转换为int。要做到这一点,需要先去掉逗号。在多次遇到这个问题后,决定放弃astype,转而依赖apply来改变列类型。
df['Price'] = df.apply(lambda x: int(x['Price'].replace(',', '')),axis=1)
最后,还有progress_apply。(tqdm)包包含了一个名为progress_apply的函数。它帮助省下了很多时间。当数据中有很多行,或者最终编写了一个相当复杂的apply函数时,可能会注意到apply需要很长时间。在使用Spacy时,看到应用需要几个小时。在这种情况下,带有进度条的apply可能会很有用。可以使用(tqdm)来做到这一点。只需在笔记本顶部的初始导入后将apply替换为progress_apply,一切都会保持不变。
from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()
new_df['rating_custom'] = df.progress_apply(lambda x: custom_rating(x['Genre'],x['Rating']),axis=1)
new_df.head()
结论:apply和lambda函数允许在修改数据时处理各种困难的任务。不认为需要担心使用Pandas时的任何事情,因为可以有效地使用它们。试图在本文中描述它的工作原理。可能有更多的方法可以实现刚刚描述的内容。然而,更喜欢apply/lambda而不是map/apply map,因为它更易于阅读,更适合工作流程。