无法播放?请 点击这里 跳转到Youtube
切换视频源:

通过上一章的学习,我们已经了解了如何定位和过滤元素,那么本章就来学习一下如何对DataFrame中的数据进行增删差改吧。

首先我们导入一个用于本章使用的数据集:

import numpy as np
import pandas as pd

csv_data_path = 'https://raw.githubusercontent.com/turingplanet/pandas-intro/main/public-datasets/small_survey_results.csv'
survey_df = pd.read_csv(csv_data_path)

此数据集包含了StackOverflow上随机的1000份用户调研,其中包含了用户的年龄、是否以编程为兴趣、对工作的满意程度、等等和编程相关的信息。(col的具体含义请参考:https://github.com/turingplanet/pandas-intro/blob/main/public-datasets/survey_results_schema.csv

这个数据集是一个真实的数据集,用它作为学习的对象可以更好地帮助我们应对真实场景中的数据处理。

排序(Sorting)

首先我们来学习一下如何给DataFrame进行排序,最简单的就是使用index排序:

survey_df.set_index('Respondent', inplace = True) # 将 Respondent 设为 index id
survey_df.sort_index() # 将Dataframe中的数据按照 index 从小打大的顺序进行排序
                                                   MainBranch Hobbyist   Age  ...
Respondent                                                                     
59                             I am a developer by profession       No  38.0   ...
146                            I am a developer by profession      Yes  33.0   ...
167                            I am a developer by profession      Yes  22.0   ...
237                            I am a developer by profession      Yes  26.0   ...
275                            I am a developer by profession       No   NaN   ...
...                                                       ...      ...   ...   ...

根据特定的列进行排序:

survey_df.sort_values(by = 'Age', ascending = False) # 按照年龄从大到小进行排序(最年长的竟然是67岁!)
survey_df.sort_values(by = ['Age', 'YearsCode'], ascending = False) # 使用多列进行排序,先按照Age进行排序,再按照YearsCode进行排序

为不同列设定不同的排序顺序:

sorted_df = survey_df.sort_values(by = ['Age', 'YearsCode'], ascending = [False, True]) # 按照Age从大到小,YearsCode从小到大进行排序
sorted_df[['Age', 'YearsCode']]
sorted_df2 = survey_df.sort_values(by = ['Age', 'YearsCode'], ascending = [0, 1]) # 和sorted_df一样,0代表False,1代表True

如果要改变原来的数据,只需要加上inplace参数即可:

survey_df.sort_values(by = ['Age', 'YearsCode'], ascending = [False, True], inplace = True)

我们也可以对其中单列数据进行排序:

survey_df['Age'].sort_values() # 将用户的年龄从小到大排序
survey_df['ConvertedComp'].nlargest(10) # 前10名工资最高的用户工资信息
survey_df['ConvertedComp'].nsmallest(10) # 工资最低的前十名用户工资信息

我们也可以通过nlargest和nsmallest将整个数据集进行排序:

richest_users = survey_df.nlargest(10, 'ConvertedComp') # 拿到工资最高的前10名用户数据
richest_users[['ConvertedComp', 'DevType', 'EdLevel']] # 查看这些用户的工资、开发类型、和教育程度
survey_df.nsmallest(10, 'ConvertedComp')[['ConvertedComp', 'DevType', 'EdLevel']] # 按低工资排序的前10名用户相关信息

迭代(Iterating)

在进入数据的增删差改之前,我们再来学习一下如何迭代我们的数据:

for col_name, col_data in survey_df.items(): # 迭代列
    print(col_name) # 打印列名
    print(col_data) # 打印列上的数据

迭代行:

for row_index, row_info in survey_df.iterrows(): # 迭代行
    print(row_index) # 打印 index
    print(row_info) # 打印单行的所有列信息

我们也可以使用iterrows函数来转置矩阵:

inversed_df = pd.DataFrame({idx: values for idx, values in survey_df.iterrows()})
inversed_df.shape # 转置矩阵:从(1000, 60)到(60, 1000)

更新列和行(add, append, delete)

接下来学习一下如何改变DataFrame中的数据,首先来学习如何改变列名:

survey_df.reset_index(inplace = True) # 先把 index 重置一下,以便后面的学习

survey_df.rename(columns = {'Age': 'user age', 'OrgSize': 'Organization Size'}, inplace = True) # 转换列名(Age => user age,OrgSize => Organization Size)
survey_df.columns = survey_df.columns.str.replace(' ', '_') # 将列名中的空格换成下划线
survey_df.columns = [col.lower() for col in survey_df.columns] # 将列名全部变为小写
survey_df['organization_size'] # 拿到原来OrgSize列的值

改变特定列上的数据:

survey_df['jobfactors'].str.lower() # 将 jobfactors 上的数据全部变成小写

如何要改变原来DataFrame上的数据,我们则需要索引来指定要改变数据的位置:

survey_df['jobfactors'] = survey_df['jobfactors'].str.lower() # 改变jobfactors列上的数据

最常用的改变数值的方法是使用 loc 函数来操作:

survey_df.loc[2, 'trans']  = 'Yes' # 将index为2的数据上的trans列改为Yes
survey_df.loc[2, ['trans', 'user_age', 'country']]  = ['Yes', 29, 'China'] # 将特定行的三列数据进行改变

我们也可以使用 filter 给用户分配数值:

survey_df.loc[survey_df['user_age'] < 18, ['age_group']]  = 'young' # 创建一个age_group列,对于那些年龄小于18的用户,将此列的数据改为为 young
survey_df.loc[~(survey_df['user_age'] < 18), ['age_group']]  = 'adult' # 对于年龄大于等于18的用户,将age_group设为adult

使用操作符号也能将多列数据进行合并:

survey_df['gender'] + survey_df['sexuality'] + survey_df['trans'] # 将性别相关的列合在一起
survey_df['gen_col'] = survey_df['gender'] + survey_df['sexuality'] + survey_df['trans'] # 将合起来的值写入gen_col列

将一个string列分开:

survey_df['job_factors'] = survey_df['jobfactors'].str.split(';') # 根据符号 ; 进行分割
survey_df['jobfactors'].str.split(';', expand = True) # 将分隔开的内容分成多列
survey_df[['job_fac1', 'job_fac2', 'job_fac3']] = survey_df['jobfactors'].str.split(';', expand = True)

添加和删除

学完了修改数据,接下来学习一下如何添加数据:

survey_df.append({'user_age': 21}) # Error will occur
survey_df.append({'user_age': 21}, ignore_index = True) # 加上 ignore_index 就能顺利添加

我们也可以将一个 DataFrame 添加到另一个 dataframe 中:

new_survey = {
   'user_age': 25,
   'country': 'China',
   'age1stcode': '24'
}
new_survey_df = pd.DataFrame(new_survey, index=[0])
survey_df.append(new_survey_df, ignore_index = True, sort = False)

接下来是和删除相关的操作:

survey_df.drop(columns = ['organization_size', 'soaccount'], inplace = True) # 删除 organization_size 和 soaccount 这两列数据
survey_df.drop(index = 4) # 删除 index 为 4 的数据
age_filter = survey_df['user_age'] < 50
survey_df.drop(index = survey_df[age_filter].index) # 删除特定满足特定条件(年龄大于50)的数据

更新数据(apply, map, applymap, replace)

学完了基本的数据更新和查询,接下来学习一下如何使用4个特别的函数(apply,map,applymap,replace)对DataFrame的数据进行更灵活的操作。

首先我们先通过以下代码,把object type的col转换成str数据类型:

dtypes = survey_df.dtypes
str_cols = [col_name for col_name in dtypes.index if dtypes[col_name] == 'object']
survey_df[str_cols] = survey_df[str_cols].astype(str)

接下来学习 apply,apply可以用于 DataFrame,可以用于 Series:

survey_df['hobbyist'].apply(len) # 查看这列数据的字符长度

我们也可以创建自定义函数来更新其中的数据:

def upper_case(col_value):
     return col_value.upper()
survey_df['hobbyist'] = survey_df['hobbyist'].apply(upper_case) # 将此列的数据全部变成大写
survey_df['hobbyist'] = survey_df['hobbyist'].apply(lambda x: x.lower()) # 使用匿名函数将此列数据全部变成小写

我们也可以对DataFrame使用apply:

survey_df.apply(len) # 查看每列的长度(有多少行)
survey_df.apply(len, axis = 'columns') # 查看每行的长度(有多少列)

survey_df.apply(pd.Series.min) # 查看每列中最小的元素
survey_df.apply(lambda x: x.min()) # 其中 x 代表一个Series,此行代码也能拿到每列中最小的数值

接下来我们来学习map,map只能用于Series:

map_dict = {'yes': 'True', 'no': 'False'}
survey_df['hobbyist'].map(map_dict) # 将yes转为True,no转为False,不match的变成NaN

map主要是针对Series的,那apply和map合起来的applymap则是针对每一个数据的:

survey_df[str_cols].applymap(len) # 针对于每个元素,对每个str数据类型的数据使用len函数
survey_df[str_cols].applymap(str.lower) # 对每个str数据类型的数据使用lower函数

最后再来学习一下replace,replace和map很类似,可以将特定数值进行转换,不同的是转换值中不包含的数据不会变成 NaN,会保持原来的数值:

map_dict = {'yes': 'True'}
survey_df['hobbyist'].replace(map_dict) # yes 会变成 True,其他保持不变
survey_df['hobbyist'].map(map_dict) # no 和 nan 都会变成 NaN

除了以上这些apply中基本的函数,Pandas中还能使用其他的NumPy函数(详情可参考 NumPy Mathematical functions:https://numpy.org/doc/stable/reference/routines.math.html)

Homework

找出所有以编程为兴趣,并且年龄小于25岁的用户。

在原来的dataframe中给这些特定用户新添一个column叫做code_years,这列的值等于user_age – age1stcode

data_filter = (survey_df['hobbyist'] == 'yes') & (survey_df['user_age'] < 25) 
filter_data = survey_df.loc[data_filter]
survey_df.loc[data_filter, 'code_years'] = filter_data['user_age'] - filter_data['age1stcode'].astype(int)