数据分析入门
一、pandas加载数据
1、根据列加载数据
格式
python
df对象[列名]
df对象.列名
例如
python
# 场景1: 加载1列数据.
# 格式: df['列名'] 或者 df.列名
df['country']
df.country
# 场景2: 加载多列数据.
# 格式: df[['列名1', '列名2'...]]
df[['country', 'year', 'lifeExp']]
2、根据行加载数据
格式
python
df对象.head() # 默认前五行,可指定行数
df对象.tail() # 默认后五行,可指定行数
df对象.loc[索引值] # 根据索引值找行
df对象.iloc[行号] # 根据行号找行
例如
python
# head(), 默认是前5行
df.head()
df.head(n=2) # 前2行
# tail(), 默认是后5行
df.tail()
df.tail(n=3) # 后3行
df.tail(n=1) # 最后1行
# loc: 根据 索引列 来获取数据的.
df.loc[0] # 第1行 => Series对象
df.loc[[0, 1, 2]] # 第1, 2, 3行 => DataFrame对象
# df.loc[-1] # 最后一行, 如果写-1, 则: 报错.
# iloc: 根据 行号 来获取数据的.
df.iloc[0] # 第1行 => Series对象
df.iloc[[0, 1, 2]] # 第1, 2, 3行 => DataFrame对象
df.iloc[-1] # 最后一行
3、加载指定行指定列的数据
格式
python
df对象.loc[[行], [列]] # 索引列值 + 列名 的方式获取
df对象.iloc[[行], [列]] # 行号 + 列的编号(索引) 的方式获取
例如
python
# 1. 精准的获取某几行的, 某几列.
df.loc[[0, 21, 211], :] # : 可以代表: 所有行, 所有列.
df.loc[[0, 21, 211], ['country', 'year', 'lifeExp']] # 获取指定列
df.iloc[[0, 21, 211], [0, 2, 3]] # 获取指定列
# 2. 获取所有行的某几列.
# 写法1: 直接传入 列名 或者 列的索引
df.loc[:, ['continent', 'pop']]
df.iloc[:, [1, 4]]
# 写法2: 可以通过 range方式来生成 索引.
df.iloc[:, range(1, 5, 2)] # 所有行, 索引为:1, 3列的信息.
df.loc[:, ['continent', 'lifeExp']]
# 写法3: 可以通过 切片的方式来生成索引.
df.iloc[:, 1:4] # 所有行, 索引为:1, 2, 3列的信息, 包左不包右.
df.loc[:, ['continent', 'year', 'lifeExp']]
# 3. 下述代码, 执行结果是什么.
df.loc[range(0, 10, 2), ['lifeExp', 'pop']]
df.iloc[range(0, 10, 2), 3: 5] # 第0, 2, 4, 6, 8行, 第3, 4列
df.iloc[range(10, -1, -2), 3: 5]
df.iloc[range(10, -2, -1), 3: 5] # 10 ~ -1 => -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 共12条数据
二、分组聚合
格式
python
# aggregate可以用agg替换
df对象.groupby([分组的列名1, 分组的列名2...]).aggregate({'要被聚合操作的字段1':'聚合函数名', '字段2':'聚合函数名'...})
# 如果需要聚合的字段都是用一个聚合函数,有语法糖写法:
df对象.groupby([分组的列名1, 分组的列名2...])[[要被聚合操作的字段1, 字段2...]].聚合函数名()
# 如果分组字段和聚合字段只有一个,有语法糖写法:
df对象.groupby('分组字段')['聚合字段'].聚合函数名()
# 聚合函数
mean() # 平均值
value_counts() # 每个值出现了几次
nunique() # 去重统计
max() # 最大值
min() # 最小值
等等
例如
python
# 场景1: 分组字段 和 聚合字段都只有1个.
# 需求1: 每年的平均预期寿命.
# 类推到MySQL中, SQL语句写法为: select year, avg(lifeExp) as 别名 from df group by year;
df.groupby('year')['lifeExp'].mean() # 细节: 分组字段会作为 索引列
# 场景2: 分组字段 => 1个, 聚合字段 => 2个
# 需求2: 每一年的平均人口和平均GDP.
df.groupby('year')[['pop', 'gdpPercap']].mean()
# 场景3: 分组字段 => 2个, 聚合字段 => 3个
# 需求3: 统计每年, 每个大洲的 平均预期寿命, 平均人口, 平均GDP.
df.groupby(['year', 'continent'])[['lifeExp', 'pop', 'gdpPercap']].mean()
# 需求4: 统计每个大洲列出了多少个国家和地区.
# 理解1: 一共有多少个大洲, 多少个国家 共计参与了多少次. 即: 总次数.
# 类推SQL写法: select continent, count(country) from df group by continent;
df.groupby('continent')['country'].value_counts() # value_counts() 每个值出现了多少次
# 理解2: 每个大洲, 有多少个国家参与. 不同的大洲, 国家 => 总数.
# 类推SQL写法: select continent, count(distinct country) from df group by continent;
df.groupby('continent')['country'].nunique() # nunique() 去重统计, 根据洲分组, 根据国家去重统计.
# 需求5: 统计每年的 平均预期寿命, 最大的总人口, 最小的GDP
# 类推SQL写法: select year, avg(lifeExp), max(pop), min(gdpPercap) from df group by year;
# pandas写法: df对象.groupby([分组的列名1, 分组的列名2...]).aggregate({'要被聚合操作的字段1':'聚合函数名', '字段2':'聚合函数名'...})
# 语法糖, df对象.groupby([分组的列名1, 分组的列名2...]).agg({'要被聚合操作的字段1':'聚合函数名', '字段2':'聚合函数名'...})
df.groupby('year').agg({'lifeExp': 'mean', 'pop': 'max', 'gdpPercap': 'min'})
df.groupby('year').aggregate({'lifeExp': 'mean', 'pop': 'max', 'gdpPercap': 'min'})
三、基本绘图
格式
python
df对象.plot.line() # 折线图
df对象.plot.bar() # 柱状图
df对象.plot.pie() # 饼图
例如
python
data = df.groupby('year')['lifeExp'].mean()
data.plot.line()
data.plot.bar()
data.plot.pie()
四、常用函数
1、查看数据函数
格式
python
df对象.shape # 维度(显示行列)
df对象.info() # 基本信息
df对象.describe() # 统计信息
例如
python
# 1. 读取数据, 获取df对象
movie = pd.read_csv('data/movie.csv')
movie.head()
# 2. 查看和数据分析相关的统计值.
movie.shape # 维度: (4916, 28) => (行数, 列数)
movie.info() # 基本信息
movie.describe() # 统计信息
movie.describe().T # 统计信息, 行列转置
movie.describe(include='all') # 统计信息, 所有字段
movie.describe(include='object') # 统计信息, 字符串类型的列的信息
movie.describe(include=object) # 语法糖写法, 效果同上
2、排序函数
格式
python
df对象.sort_values(列名) # 指定列的值排序
df对象.sort_values([列名1, 列名2...], ascending=[True, False]) # 多个列的值排序
df对象.sort_index() # 按照索引排序
df对象.nlargest(个数, 列名) # 取目标列最大的几个值
df对象.nsmallest(个数, 列名) # 取目标列最小的几个值
df对象.drop_duplicates(列名) # 删除重复的数据
df对象.drop_duplicates([列名1, 列名2...]) # 删除多列一起的重复的数据
例如
python
# 1. 从movie这个df对象中, 找到我们要用的列的信息.
movie_df = movie[['movie_title', 'imdb_score', 'budget']]
movie_df.head()
# 2. 找到 高口碑(评分最高的前100部电影)
# movie_df.sort_values('imdb_score', ascending=False).head(100)
tmp_df = movie_df.nlargest(100, 'imdb_score') # 效果同上.
# 3. 在高口碑的数据基础上, 找到 成品最低的那 10 部电影.
# tmp_df.sort_values('budget').head(10) # ascending默认是True, 升序
tmp_df.nsmallest(10, 'budget') # 效果同上.
# 4. 上述代码, 一行搞定.
movie[['movie_title', 'imdb_score', 'budget']].nlargest(100, 'imdb_score').nsmallest(10, 'budget')
3、取值函数
格式
python
df对象.nlargest(个数, 列名) # 取目标列最大的几个值
df对象.nsmallest(个数, 列名) # 取目标列最小的几个值
例如
python
# 1. 从movie这个df对象中, 找到我们要用的列的信息.
movie_df = movie[['movie_title', 'imdb_score', 'budget']]
movie_df.head()
# 2. 找到 高口碑(评分最高的前100部电影)
# movie_df.sort_values('imdb_score', ascending=False).head(100)
tmp_df = movie_df.nlargest(100, 'imdb_score') # 效果同上.
# 3. 在高口碑的数据基础上, 找到 成品最低的那 10 部电影.
# tmp_df.sort_values('budget').head(10) # ascending默认是True, 升序
tmp_df.nsmallest(10, 'budget') # 效果同上.
# 4. 上述代码, 一行搞定.
movie[['movie_title', 'imdb_score', 'budget']].nlargest(100, 'imdb_score').nsmallest(10, 'budget')
4、数据清洗函数
格式
python
df对象.dropna() # 删除包含缺失值的行或列
df对象.fillna(值) # 将缺失值替换为指定的值
df对象.replace(旧值, 新值) # 新值替换旧值
df对象.duplicated() # 检查是否有重复的数据
df对象.drop_duplicates(列名) # 删除重复的数据,保留一份
df对象.drop_duplicates([列名1, 列名2...]) # 删除多列一起的重复的数据
例如
python
# 1. 获取到我们要处理的数据集.
movie_df = movie[['title_year', 'movie_title', 'imdb_score']] # 电影名, 评分, 年费
movie_df.head()
# 2. 按照年排序, 降序.
movie_df.sort_values('title_year', ascending=False)
# 3. 按照年, 评分 降序排序.
tmp_df = movie_df.sort_values(['title_year', 'imdb_score'], ascending=False)
# 4. 基于上述的临时数据, 按照 年份去重, 保留第1份即可.
# subset参数: 参考哪一列进行去重.
tmp_df.drop_duplicates(subset='title_year')
# 5. 一行代码搞定.
# 完整代码
movie[['title_year', 'imdb_score', 'movie_title']].sort_values(['title_year', 'imdb_score'], ascending=[False, False]).drop_duplicates(subset='title_year')
# 语法糖
movie[['title_year', 'imdb_score', 'movie_title']].sort_values(['title_year', 'imdb_score'], ascending=False).drop_duplicates('title_year')
五、数据拼接
1、concat函数
格式
python
pandas.concat([df对象1, df对象2, df对象3 ...], axis='rows/columns', ignore_index=True)
'''
第一个参数:需要拼接的df对象,用列表包裹
axis参数:rows为行拼接,columns为列拼接
ignore_index参数:是False时,默认的索引;时True时,忽略索引,默认用0~n填充。默认是False
拼接未匹配的数据用NAN填充
'''
(一)、df对象和df对象拼接
python
# 记忆: concat()函数既能实现行拼接(默认), 也能实现列拼接. 行拼接参考: 列名, 列拼接参考: 索引列(行索引)
# 1. 演示行拼接
pd.concat([df1, df2, df3]) # 默认是: 行拼接.
pd.concat([df1, df2, df3], axis='rows') # 效果同上
pd.concat([df1, df2, df3], axis=0) # 效果同上, 0 => rows, 行, 1 => columns, 列
# 2. 演示列拼接
pd.concat([df1, df2, df3], axis='columns') # 列拼接
pd.concat([df1, df2, df3], axis=1) # 效果同上
# 3. 演示 行, 列拼接时, 重置: 索引 和 列名
# 细节: 无论是行, 列拼接时, 只要忽略索引了, 都会默认用 0 ~ n来填充.
pd.concat([df1, df2, df3], axis='rows', ignore_index=True) # 行拼接
pd.concat([df1, df2, df3], axis='columns', ignore_index=True) # 列拼接
# 4. 拼接 df1 和 df4
# 行拼接, 参考: 列名
pd.concat([df1, df4], axis='rows') # 未匹配, 用NAN填充.
# 列拼接, 参考: 索引列
pd.concat([df1, df4], axis='columns') # 未匹配, 用NAN填充.
(二)、df对象和Series对象拼接
python
'''
当进行行拼接时,需要对比列名,如果列名相同就并入目标列,如果不相同就单开一列,空值用NAN填充
当进行列拼接时,需要对比行索引,如果行索引相同就并入目标行,如果不相同就单开一行,空值用NAN填充
'''
# 1. 创建Series对象.
s1 = pd.Series(['n1', 'n2', 'n3'])
# 2. 使用concat拼接df和series对象.
pd.concat([df1, s1], axis='rows') # 默认是: 行拼接.
pd.concat([df1, s1], axis='columns') # 默认是: 列拼接.
(三)、df对象新增一列
python
# 方式一:df对象[列名] = 列表, 要求: 列表的长度 要和 df的行数一致
df1['new_col1'] = [10, 20, 30, 40]
# 方式二:df对象[列名] = Series对象, Series对象值的个数无要求
'''
少的值用NAN填充
数量一样则正常填充
数量大于目标行/列则只取前n个,多余的丢弃
'''
df1['new_col2'] = pd.Series([1, 2, 3])
df1['new_col3'] = pd.Series([1, 2, 3, 4])
df1['new_col4'] = pd.Series([1, 2, 3, 4, 5])
2、merge函数
concat_1.csv concat_2.csv concat_3.csv
格式
python
df对象.merge(df对象/Series对象, on='关联字段', how='链接方式', suffixes='后缀')
'''
参1: 要被合并的df对象.
参2: on表示两个df合并的 关联字段, 如果一样可以直接写 on, 如果不一样, 则要写 left_on='左表字段名', right_on='右表字段名'
参3: how表示合并方式, 内连接: inner, 左连接: left, 右连接: right, 全(满)连接: outer
'''
**总结: **
merge的格式. df1.merge(df2, on='关联字段', how='连接方式')
**细节: **
**1. 默认是inner. **
**2. 关联字段不一致, 用 left_on 和 right_on **
3.两个df的字段有重名, 可以通过 suffixes 解决.
(一)、一对一关系
例子
python
# 准备动作
# 1. 创建连接对象, 关联: *.db文件.
conn = sqlite3.connect('data/chinook.db')
# 2. 从上述的文件中, 读取 歌曲表的信息.
# 参1: 要执行的SQL语句, 参2: 连接对象.
tracks_df = pd.read_sql_query('select * from tracks;', conn)
tracks_df.head()
# 3. 从上述的文件中, 读取 歌曲分类表的信息.
genres_df = pd.read_sql_query('select * from genres;', conn)
genres_df
# 4. 查看 歌曲表的信息, 并从中找到 不同的音乐风格的数据
tracks_subset_df = tracks_df.loc[[0, 62, 76, 98, 110, 193, 204, 281]]
tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']]
# 场景1: 内连接
genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='inner')
# 场景2: 左外连接
genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='left')
# 场景3: 右外连接
genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='right')
# 场景4: 满外连接, 也叫: 全连接, 即: 它的查询结果 = 左连接 + 右连接, 即: 左表全集 + 右表全集 + 交集.
genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='outer')
# 场景5: 查看默认是哪种连接.
genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId') # 默认是: 内连接 => inner
# 场景6: 如果关联的多个df有重名的列, 则默认会加上 _x, _y这样的后缀, 来源于: suffixes字段.
genres_df.merge(tracks_subset_df[['TrackId', 'Name', 'GenreId', 'Milliseconds']], on='GenreId') # 默认后缀: _x, _y
genres_df.merge(tracks_subset_df[['TrackId', 'Name', 'GenreId', 'Milliseconds']], on='GenreId', suffixes=('_left', '_right')) # 默认后缀: _x, _y
(二)、一对多关系
python
# 1. 合并 genres(风格表) 和 tracks(歌曲表)
genres_df.merge(tracks_df[['TrackId', 'Name', 'GenreId', 'Milliseconds']], on='GenreId')
# 需求2: 计算每种类型音乐的平均时长.
# 1. 合并 genres(风格表) 和 tracks(歌曲表). 交集.
genre_track = genres_df.merge(tracks_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='inner') # 风格表.merge(歌曲表['歌曲id', '风格id', '歌曲时长毫秒'])
# 左外连接.
genre_track = genres_df.merge(tracks_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='left') # 风格表.merge(歌曲表['歌曲id', '风格id', '歌曲时长毫秒'])
genre_track
# 2. 根据 风格id分组, 计算 时长的平均值.
genre_time = genre_track.groupby(['GenreId', 'Name']).Milliseconds.mean()
genre_time
# 3. 扩展, 后边详解. 把上述的 genre_time => 秒.
pd.to_timedelta(genre_time, unit='ms').dt.floor('s')
# pd.to_timedelta(genre_time, unit='ms') 意思是: 把 genre_time 的毫秒数, 转换成 pandas.Timedelta 类型.
# dt.floor('s') 意思是: 取整, 取秒.
pd.to_timedelta(genre_time, unit='ms').dt.floor('s').sort_values()
3、join函数
stocks_2016.csv stocks_2017.csv stocks_2018.csv
格式
python
df对象.join(df1对象/Series对象, lsuffix='别名1', rsuffix='别名2', on='匹配列', how='关联方式')
'''
参1:选择要连接的对象
参2:如果有重名的字段,则需要起别名来区分,此处别名为df
参3:如果有重名的字段,则需要起别名来区分,此处别名为df1
参4:on设定函数外df对象的普通列
参5:how是指定关联方式, 内连接: inner, 左连接: left, 右连接: right, 全(满)连接: outer
'''
总结:
1. 默认是 左外连接.
2. 如果两个df有重名字段, 需要手动设置后缀名.
3. 默认是根据两个df的 索引列来合并的, 如果想要关联普通列, 需要通过 on 参数实现.
例子
python
# 1. 加载数据, 获取df对象.
stock_2016 = pd.read_csv('data/stocks_2016.csv')
stock_2017 = pd.read_csv('data/stocks_2017.csv')
stock_2018 = pd.read_csv('data/stocks_2018.csv')
stock_2016
stock_2017
stock_2018
# 2. 默认情况下, join会参考 两个df的 索引列 进行合并连接.
stock_2016.join(stock_2017, lsuffix='_2016', rsuffix='_2017') # 默认: 左外连接
stock_2016.join(stock_2017, lsuffix='_2016', rsuffix='_2017', how='left') # 效果同上
# 3. 设置两个df对象的 Symbol列为索引列, 再次关联.
stock_2016.set_index('Symbol')
stock_2017.set_index('Symbol')
# 设置索引列, 并关联.
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='left') # 左外连接
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='right') # 右外连接
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='outer') # 满外连接
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='inner') # 内连接
# 4. 设置stock_2016的索引为: Symbol 和 stock_2018做关联.
stock_2016
stock_2018.set_index('Symbol')
# 拿着 stock_2016的 指定列(普通列) 和 stock_2018的 索引列 进行关联.
# 细节: on参数设定的是 函数外 df对象的 普通列
stock_2016.join(stock_2018.set_index('Symbol'), lsuffix='_left', rsuffix='_right', on='Symbol', how='outer')
六、缺失值处理
1、缺失值的查看和比较
在pandas库中,缺失值来源于numpy中的NAN,nan,NaN,他们都是空,需要注意的是,空代表什么都没有,也就是说,空不能等于空.
isnull():判断是否为空
isna():判断是否为空
notnull():判断是否不为空
notna():判断是否不为空
python
# 细节: 在Pandas中, 缺失值来源于numpy包的NAN, nan, NaN, 他们都表示空.
# from numpy import NAN, nan, NaN
# 1. 空值比较.
print(np.NAN == True) # False
print(np.NAN == False) # False
print(np.NAN == '') # False
print(np.NAN == 0) # False
# 2. 空和空比较, 也都是False.
print(np.NAN == np.nan) # False
print(np.NAN == np.NaN) # False
print(np.nan == np.NaN) # False
# 3. 判断是否为空. Pandas库的 isnull(), isna(), notnull(), notna()
print(pd.isnull(np.NAN)) # True
print(pd.isnull('')) # False
print(pd.notnull(np.NAN)) # False
print(pd.notnull('')) # True
2、加载数据时操作缺失值
格式
python
对象.read_csv('路径', keep_default_na=False) # keep_default_na: 设置加载时是否加载缺失值. True(默认): 加载. False: 不加载
对象.read_csv('路径', na_values=['值']) # na_values: 设置加载时, 哪些值 设置为缺失值
例子
python
# 1. 读取数据, 获取df对象.
pd.read_csv('data/survey_visited.csv') # 默认: 会加载缺失值.
# keep_default_na: 设置加载时是否加载缺失值. True(默认): 加载. False: 不加载.
pd.read_csv('data/survey_visited.csv', keep_default_na=False)
# na_values: 设置加载时, 哪些值 设置为缺失值.
pd.read_csv('data/survey_visited.csv', na_values=['734', '751', 'MSK-4', '1939-01-07'])
3、删除缺失值
格式
python
df对象.dropna(axis='rows') # 按照: 行 删除空值
df对象.dropna(axis=0) # 按照: 行 删除空值
df对象.dropna(axis='columns') # 按列删除缺失值
df对象.dropna(axis=1) # 按列删除缺失值
# subset参数: 参考的列, 即: 该列值为空, 才会删除行, 或者 列.
# how参数: 删除方式, any: 只要有空值, 就删除行或者列. all: 全部为空, 才删除行或者列.
df对象.dropna(subset=['列名', '列名'...], how='all')
df对象.dropna(subset=['列名', '列名'...], how='any')
例子
python
# 1. 查看 df 对象
train.isnull().sum() # 查看各列的空值情况.
# 2. 删除缺失值.
# 按 行 删除缺失值.
train.dropna()
train.dropna(axis='rows') # 效果同上, 默认按照: 行 删除空值.
train.dropna(axis=0) # 效果同上, 默认按照: 行 删除空值.
# 按 列 删除缺失值
train.dropna(axis='columns') # 按列删除缺失值.
train.dropna(axis=1) # 效果同上.
# subset参数: 参考的列, 即: 该列值为空, 才会删除行, 或者 列.
# how参数: 删除方式, any: 只要有空值, 就删除行或者列. all: 全部为空, 才删除行或者列.
train.dropna(subset=['Age', 'Embarked'], how='all')
train.dropna(subset=['Age', 'Embarked'], how='any')
# 查看删除后的数据
train.dropna(subset=['Age', 'Embarked'], how='any').isnull().sum()
4、填充缺失值
(一)、非时间序列,固定值
格式
python
df对象.fillna(值) # 填充所有的空值
例子
python
# 1. 查看数据集.
train.isnull().sum()
# 2. 填充缺失值, 用 固定值填充.
train.fillna(0) # 填充所有的空值.
# 3. 查看填充后的数据集.
train.fillna(0).isnull().sum()
(二)、时间序列,非固定值
格式
python
ffill() # 空值的上一个值
bfill() # 空值的下一个值
# 采用 线性插入法, 即: 结合上下值, 计算出结果, 并填充
# both: 结合上下值. forward: 结合上1个值, backward: 结合下一个值
interpolate(limit_direction='forward'|'both'|'backward')
例子
python
# 时间序列填充 = 结合数据列的上下文的值, 计算出要填充的值.
# 1. 加载数据, 获取df对象.
city_day = pd.read_csv('data/city_day.csv', parse_dates=['Date'], index_col='Date')
city_day
# 2. 从中提取出一些数据, 二甲苯列的数据.
city_day.Xylene[50:64]
# 3. 采用 时间序列填充, 参考: 空值的上一个值.
# city_day.Xylene[50:64].fillna(method='ffill') # 已过时.
city_day.Xylene[50:64].ffill() # 推荐写法
# 4. 采用 时间序列填充, 参考: 空值的下一个值.
# city_day.Xylene[50:64].fillna(method='bfill') # 已过时.
city_day.Xylene[50:64].bfill() # 推荐写法
# 5. 采用 线性插入法, 即: 结合上下值, 计算出结果, 并填充.
# both: 结合上下值. forward: 结合上1个值, backward: 结合下一个值.
city_day.interpolate(limit_direction='forward').Xylene[50:64]
七、apply自定义函数
1、操作Series对象
格式
python
# apply()函数操作Series对象, 是把Series的逐个值进行传入并操作的
series对象.apply(函数对象)
例子
python
# 1. 定义1个df对象.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
# 2. 定义1个函数, 用于求 值 的 平方. 2 => 4, 5 => 25
def my_fun1(x):
print('看看我执行了嘛!')
return x ** 2
# 扩展: 定义函数, 计算x的e次方.
def my_fun2(x, e):
return x ** e
# 3. 把上述的函数, 作用于 df对象的 a列值(Series对象)
df['a'].apply(my_fun1) # 细节: 这里写的是函数名, 即: 函数对象. 如果写: 函数名() 则表示是在调用函数.
df.a.apply(my_fun2, e=3) # 细节: 传参数时, 使用 关键字参数 写法进行传参.
2、操作DataFrame对象
格式
python
# apply()函数操作DataFrame对象, 是把DataFrame的整列/整行进行传入并操作的
series对象.apply(函数对象)
例如
python
# 1. 定义1个df对象.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
# 2. 把上述的函数, 作用于 df对象
df.apply(my_fun1) # 默认: axis=0(列), 即: 整列值进行传入, 进行操作.
# 3. 计算每列的平均值, 尝试用如下的函数做, 发现报错. 最终验证 => df的apply()函数, 默认是传入整列值的, 而不是逐个值进行传入的.
def my_fun3(x, y, z):
return (x + y + z) / 3
df.apply(my_fun3) # 报错,因为默认是传入整列值
# 4. 分析上述报错原因, 得出: apply()函数默认传入的是整列值, 而不是逐个值.
def my_fun4(x):
print(x)
print(type(x))
df.apply(my_fun4)
# 5. 定义函数, 计算df对象 每列的平均值
def my_fun5(x):
# 这里的x可以是: df对象的整行 或者 整列数据
return x.mean()
df.apply(my_fun5) # 默认: axis=0(列)
df.apply(my_fun5, axis=0) # 效果同上
# 6. 定义函数, 计算df对象 每行的平均值
df.apply(my_fun5, axis=1)
3、向量化函数
格式
python
# 通过 np.vectorize()函数, 将自定义函数向量化. 即: 如果遇到了向量, 则会逐个进行遍历, 获取标量并操作
# 写法一
@np.vectorize
# 写法二
函数名 = np.vectorize(函数名)
例如
python
# 1. 定义1个df对象.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
# 2. 自定义函数, 接收 df对象的 两列数据, 计算 每行的平均值.
# 装饰的写法1: @装饰器名
@np.vectorize
def my_fun6(x, y):
# 判断, 如果x的值是20, 就返回NaN
if x == 20: # 报错: x是向量, 20是标量, 向量和标量无法直接计算.
return np.NAN
# for i in x:
# if i == 20: # 手动遍历, 就不报错了, 但是结果不是我们要的.
# return np.NAN
# x代表第1列数据, y代表第2列数据
return (x + y) / 2
# 调用函数
my_fun6(df.a, df.b)
# 3. 可以通过 np.vectorize()函数, 将自定义函数向量化. 即: 如果遇到了向量, 则会逐个进行遍历, 获取标量并操作.
# 装饰的写法2: 传统写法.
my_fun6 = np.vectorize(my_fun6) # 装饰后的函数对象 = 装饰器(要被装饰的函数名)
my_fun6(df.a, df.b)
4、结合lambda表达式使用
将lambda表达式作为函数对象传入apply中。
python
# 1. 定义数据集.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
# 2. 需求: 每个值 => 该值的平方.
def my_fun1(x):
return x ** 2
df.apply(my_fun1)
# 3. 上述的需求可以用 Lambda表达式来完成.
df.apply(lambda x : x ** 2)
df.apply(lambda x : x.mean())
df.apply(lambda x : x.mean(), axis=0) # 效果同上.
df.apply(lambda x : x.mean(), axis=1) # 统计每行的平均值
八、分组操作
1、单变量分组聚合
格式
python
df对象.groupby('列名')
例如
python
# 写法1
df.groupby('year')['lifeExp'].mean()
# 写法2
df.groupby('year').lifeExp.mean()
# 上述都是一步到位, 直接计算结果, 我们也可以手动计算.
# 1. 我们先看看一共有多少个年
df.year.unique() # 12个年份, 底层算 12 次即可, 这里我们就用 1952年举例.
# 2. 获取1952年所有的数据, 计算平均寿命
df[df['year'] == 1952].lifeExp.mean()
df[df.year == 1952].lifeExp.mean() # 效果同上.
2、分组+转换
格式
python
对象.groupby('列名').列名.apply(聚合函数) # n => 1 聚合的效果
对象.groupby('列名').列名.transform(聚合函数) # n => n 类似于: MySQL的窗口函数的效果
例子
python
# 需求1: 计算x的 z-score分数, 也叫: 标准分数, 公式为: (x - x_mean) / x_std
# 1. 读取数据, 获取df对象
df = pd.read_csv('data/gapminder.tsv', sep='\t')
# 2. 定义函数, 计算某列的 z-score分数.
def my_zscore(col):
return (col - col.mean()) / col.std() # (列值 - 平均值) / 标准差
# 3. 调用上述的格式.
df.groupby('year').lifeExp.apply(my_zscore) # 1704条
# 需求2: 分组填充案例
# 需求: 读取文件(小票信息), 获取df对象. 其中有1列 total_bill 表示总消费. 随机抽取4个缺失值, 然后进行填充.
# 填充方式: 每个组的平均值. 即: 如果是Male => 就用 Male列的平均值填充, 如果是Female => Female列的平均值填充.
# 1. 读取文件, 获取DataFrame对象
df = pd.read_csv('data/tips.csv')
# 2. 抽样方式, 从上述的df对象中, 随机抽取10条数据.
# tips_10 = df.sample(10) # 这里的10表示随机抽取 10 条数据.
# random_state: 随机种子, 只要种子一样, 每次抽取的数值都是一样的.
tips_10 = df.sample(10, random_state=21)
tips_10
# 3. 随机的从上述的10条数据中, 抽取4行数据, 设置他们的 total_bill(消费总金额) 为 NaN
# 写法1: 每次固定 这四条数据 的 total_bill为 空值.
# tips_10.loc[[173, 240, 243, 175], 'total_bill'] = np.NaN
# 写法2: 每次随机4条数据, 设置它们的 total_bill为 空值.
# np.random.permutation()解释: 随机打乱索引值, 并返回打乱后的索引值.
tips_10.loc[np.random.permutation(tips_10.index)[:4], 'total_bill'] = np.NaN
tips_10
# 4. 分别计算 Male 和 Female 的平均消费金额, 用于填充对应组的 缺失值.
# 思路1: 直接用 整体的 总消费金额的 平均值 填充.
tips_10.fillna(tips_10.total_bill.mean())
# 思路2: 自定义函数, 计算每组的平均消费金额, 进行填充
def my_mean(col):
# return col.sum() / col.size # 某列总金额 / 某列元素个数, 这种写法会导致: 本组所有的数据都会被新值覆盖.
return col.fillna(col.mean()) # 用该列的平均值, 来填充该列的缺失值, 其它不变.
# 调用上述函数, 实现: 分组填充, 即: 给我N条, 处理后, 还是返回N条数据.
# tips_10.groupby('sex').total_bill.apply(my_mean) # n => 1 聚合的效果.
tips_10.groupby('sex').total_bill.transform(my_mean) # n => n 类似于: MySQL的窗口函数的效果.
3、分组+过滤
格式
python
对象.groupby('列名').filter(条件)
对象.query(条件)
例子
python
# 1. 读取文件, 获取DataFrame对象
df = pd.read_csv('data/tips.csv')
# 2. 查看用餐人数情况.
tmp_df = df.groupby('size', as_index=False).total_bill.count()
tmp_df.columns = ['size', 'count']
tmp_df
df.size # 这样写, 会把 size当做 属性, 而不是 size列.
df['size'].value_counts()
# 3. 我们发现, 在所有的 消费记录中, 就餐人数 在 1, 5, 6个人的消费次数相对较少, 我们可以过滤掉这部分的数据
tmp_df = df.groupby('size').filter(lambda x : x['size'].count() > 30)
tmp_df
# 4. 验证上述筛选后的数据, size列只有 2, 3, 4 这三种就餐人数的情况.
tmp_df['size'].value_counts()
# 5. 上述代码的合并版, 一行搞定.
df.groupby('size').filter(lambda x : x['size'].count() > 30)['size'].value_counts()
# 另外一种筛选的方式, 可以基于: query()函数 + 筛选条件, 找出要的合法的数据.
df.query('size == 2 or size == 3 or size == 4')
df.query('size in [2, 3, 4]')
4、DataFrameGroupby df的分组对象
格式
python
# 分组对象不能使用 0 索引获取数据,要获取数据, 可以通过 grouped.get_group() 函数实现
DataFrameGroupBy对象.get_group(('列名'))
例子
python
# 1. 从小费数据中, 随机的获取10条数据.
tips_10 = pd.read_csv('data/tips.csv').sample(10, random_state=21)
tips_10
# 2. 演示 根据性别分组, 获取: 分组对象.
grouped = tips_10.groupby('sex') # DataFrameGroupBy 对象
grouped
# 3. 遍历上述的分组对象, 看看每个分组都是啥(即: 每个分组的数据)
for sex_group in grouped:
print(sex_group) # sex_group: 就是具体的每个分组的数据.
# 4. 获取指定的某个分组的数据.
grouped.get_group('Male')
grouped.get_group('Female')
# 5. 需求: 使用groupby() 按 性别 和 用餐时间分组, 计算小费数据的平均值.
df.groupby(['sex', 'time']).tip.mean()
# 6. 分组对象不能使用 0 索引获取数据
# grouped[0] # 分组对象不能使用 0 索引获取数据, 要获取数据, 可以通过 grouped.get_group() 函数实现
grouped.get_group('Male')
5、透视表
格式
python
# 参1: index, 索引列, 等价于: groupby()的分组字段.
# 参2: columns, 列索引, 要写的也是 原表中的 列名.
# 参3: values, 统计字段, 等价于: groupby()的聚合字段.
# 参4: aggfunc, 聚合函数, 默认为: mean
df对象.pivot_table(index='索引列', values='统计字段', aggfunc='聚合函数')
# 扩展:
# 可视化数据.
# 绘制 月增量, figsize=宽高, color=颜色, secondary_y: 启用双Y轴, legend: 图例, grid: 网格, xlabel=X轴标签, ylabel=Y轴标签
对象.列名.plot(figsize=(20, 10), color='red', secondary_y=True, legend=True)
例子
python
# 可视化上述的数据.
# 1. 构建画布(画板), 坐标系.
fig, ax1 = plt.subplots(figsize=(20, 10))
# 2. 基于ax1坐标轴对象, 构建ax2 坐标轴对象.
ax2 = ax1.twinx()
# 3. 绘制 白银, 黄金会员分布情况.
member_rating[1:][['白银会员', '黄金会员']].plot(kind='bar', ax=ax1, legend=True, xlabel='年月', ylabel='白银/黄金')
# 4. 绘制 铂金, 钻石会员分布情况.
member_rating[1:][['铂金会员', '钻石会员']].plot(ax=ax2, legend=True, ylabel='铂金/钻石', grid=True, color=['pink', 'green'])
# 5. 设置 ax2 坐标轴的图例 到 画布的 左上角.
ax2.legend(loc='upper left')
# 6. 设置标题
plt.title('增量等级分布', fontsize=21)
# 7. 绘制图形.
plt.show()
九、日期时间处理
1、pandas中的日期类型
格式
python
对象.to_datetime('时间') # Timestamp('时间:年月日时分秒')
对象.Timestamp('时间') # Timestamp('时间:年月日时分秒')
例子
python
d1 = pd.to_datetime('2024-10-01') # Timestamp('2024-10-01 00:00:00')
d2 = pd.Timestamp('2024-10-01') # Timestamp('2024-10-01 00:00:00')
2、提取日期中的各个部分
格式
python
对象.to_datetime('时间') # Timestamp('时间:年月日时分秒')
对象.year # 年
对象.month # 月
对象.day # 日
对象.dayofyear # 一年的第几天
对象.Timestamp('时间') # Timestamp('时间:年月日时分秒')
例子
python
d1 = pd.to_datetime('2024-10-01') # Timestamp('2024-10-01 00:00:00')
d2 = pd.Timestamp('2024-10-01') # Timestamp('2024-10-01 00:00:00')
3、生成日期序列
格式
python
对象.date_range(start, end, freq='生成规则') # 包左包右
例子
python
# 1. 获取 2024年9月, 10月的日期序列.
pd.date_range('2024-09-01', '2024-10-31') # 默认: freq='D', D => 日历日
# 2. 获取 2024年9月, 10月, 获取所有的工作日时间
pd.date_range('2024-09-01', '2024-10-31', freq='B') # 不间隔工作日.
'''
DatetimeIndex(['2024-09-02', '2024-09-03', '2024-09-04', '2024-09-05',
'2024-09-06', '2024-09-09', '2024-09-10', '2024-09-11',
'2024-09-12', '2024-09-13', '2024-09-16', '2024-09-17',
'2024-09-18', '2024-09-19', '2024-09-20', '2024-09-23',
'2024-09-24', '2024-09-25', '2024-09-26', '2024-09-27',
'2024-09-30', '2024-10-01', '2024-10-02', '2024-10-03',
'2024-10-04', '2024-10-07', '2024-10-08', '2024-10-09',
'2024-10-10', '2024-10-11', '2024-10-14', '2024-10-15',
'2024-10-16', '2024-10-17', '2024-10-18', '2024-10-21',
'2024-10-22', '2024-10-23', '2024-10-24', '2024-10-25',
'2024-10-28', '2024-10-29', '2024-10-30', '2024-10-31'],
dtype='datetime64[ns]', freq='B')
'''
# 3. 获取 2024年9月, 10月, 间隔1个工作日, 获取1个日期.
pd.date_range('2024-09-01', '2024-10-31', freq='2B')
'''
DatetimeIndex(['2024-09-02', '2024-09-04', '2024-09-06', '2024-09-10',
'2024-09-12', '2024-09-16', '2024-09-18', '2024-09-20',
'2024-09-24', '2024-09-26', '2024-09-30', '2024-10-02',
'2024-10-04', '2024-10-08', '2024-10-10', '2024-10-14',
'2024-10-16', '2024-10-18', '2024-10-22', '2024-10-24',
'2024-10-28', '2024-10-30'],
dtype='datetime64[ns]', freq='2B')
'''
# 4. 获取 2024年9月, 10月, 每个月的第1个周四. Week Of Month, Thursday => 周四
pd.date_range('2024-09-01', '2024-10-31', freq='WOM-1THU')
'''
DatetimeIndex(['2024-09-05', '2024-10-03'], dtype='datetime64[ns]', freq='WOM-1THU')
'''
# 5. 获取 2024年9月, 10月, 每个月的第3个周五. Week Of Month, Friday => 周五
pd.date_range('2024-09-01', '2024-10-31', freq='WOM-3FRI')
'''
DatetimeIndex(['2024-09-20', '2024-10-18'], dtype='datetime64[ns]', freq='WOM-3FRI')
'''