自学内容网 自学内容网

数据分析入门

数据分析入门

一、pandas加载数据

1、根据列加载数据

格式

df对象[列名]
df对象.列名

例如

# 场景1: 加载1列数据. 
# 格式: df['列名'] 或者 df.列名
df['country']
df.country

# 场景2: 加载多列数据.
# 格式: df[['列名1', '列名2'...]]
df[['country', 'year', 'lifeExp']]

2、根据行加载数据

格式

df对象.head()# 默认前五行,可指定行数
df对象.tail()# 默认后五行,可指定行数
df对象.loc[索引值]  # 根据索引值找行
df对象.iloc[行号]   # 根据行号找行

例如

# 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、加载指定行指定列的数据

格式

df对象.loc[[], []]        # 索引列值 + 列名 的方式获取
df对象.iloc[[], []]       # 行号 + 列的编号(索引) 的方式获取

例如

# 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条数据

二、分组聚合

格式

# aggregate可以用agg替换
df对象.groupby([分组的列名1, 分组的列名2...]).aggregate({'要被聚合操作的字段1':'聚合函数名', '字段2':'聚合函数名'...})


# 如果需要聚合的字段都是用一个聚合函数,有语法糖写法:
df对象.groupby([分组的列名1, 分组的列名2...])[[要被聚合操作的字段1, 字段2...]].聚合函数名()


# 如果分组字段和聚合字段只有一个,有语法糖写法:
df对象.groupby('分组字段')['聚合字段'].聚合函数名()


# 聚合函数
mean()# 平均值
value_counts()# 每个值出现了几次
nunique()# 去重统计
max()# 最大值
min()# 最小值
等等

例如

# 场景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'})

三、基本绘图

格式

df对象.plot.line()  # 折线图    
df对象.plot.bar()   # 柱状图
df对象.plot.pie()   # 饼图

例如

data = df.groupby('year')['lifeExp'].mean()
data.plot.line()
data.plot.bar()
data.plot.pie()

四、常用函数

1、查看数据函数

格式

df对象.shape# 维度(显示行列)
df对象.info()# 基本信息
df对象.describe()# 统计信息

例如

# 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、排序函数

格式

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...])   # 删除多列一起的重复的数据

例如

# 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、取值函数

格式

df对象.nlargest(个数, 列名)# 取目标列最大的几个值
df对象.nsmallest(个数, 列名)# 取目标列最小的几个值

例如

# 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、数据清洗函数

格式

df对象.dropna()# 删除包含缺失值的行或列
df对象.fillna()# 将缺失值替换为指定的值
df对象.replace(旧值, 新值)  # 新值替换旧值

df对象.duplicated() # 检查是否有重复的数据
df对象.drop_duplicates(列名)# 删除重复的数据,保留一份
df对象.drop_duplicates([列名1, 列名2...])   # 删除多列一起的重复的数据

例如

# 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函数

格式

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对象拼接
# 记忆: 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对象拼接
'''
当进行行拼接时,需要对比列名,如果列名相同就并入目标列,如果不相同就单开一列,空值用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对象新增一列
# 方式一: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

格式

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 解决.

(一)、一对一关系

例子

# 准备动作
# 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
(二)、一对多关系
# 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

格式

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 参数实现.

例子

# 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():判断是否不为空

# 细节: 在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、加载数据时操作缺失值

格式

对象.read_csv('路径', keep_default_na=False)# keep_default_na: 设置加载时是否加载缺失值.  True(默认): 加载.  False: 不加载

对象.read_csv('路径', na_values=['值'])# na_values: 设置加载时, 哪些值 设置为缺失值

例子

# 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、删除缺失值

格式

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')

例子

# 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、填充缺失值

(一)、非时间序列,固定值

格式

df对象.fillna()# 填充所有的空值

例子

# 1. 查看数据集.
train.isnull().sum()


# 2. 填充缺失值, 用 固定值填充. 
train.fillna(0)     # 填充所有的空值.


# 3. 查看填充后的数据集.
train.fillna(0).isnull().sum()
(二)、时间序列,非固定值

格式

ffill()# 空值的上一个值

bfill()# 空值的下一个值

# 采用 线性插入法, 即: 结合上下值, 计算出结果, 并填充
# both: 结合上下值.  forward: 结合上1个值, backward: 结合下一个值
interpolate(limit_direction='forward'|'both'|'backward')

例子

# 时间序列填充 = 结合数据列的上下文的值, 计算出要填充的值. 
# 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对象

格式

# apply()函数操作Series对象, 是把Series的逐个值进行传入并操作的
series对象.apply(函数对象)

例子

# 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对象

格式

# apply()函数操作DataFrame对象, 是把DataFrame的整列/整行进行传入并操作的
series对象.apply(函数对象)

例如

# 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、向量化函数

格式

# 通过 np.vectorize()函数, 将自定义函数向量化. 即: 如果遇到了向量, 则会逐个进行遍历, 获取标量并操作
# 写法一
@np.vectorize

# 写法二
函数名 = np.vectorize(函数名)

例如

# 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中。

# 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、单变量分组聚合

格式

df对象.groupby('列名')

例如

# 写法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、分组+转换

格式

对象.groupby('列名').列名.apply(聚合函数)# n => 1  聚合的效果
对象.groupby('列名').列名.transform(聚合函数)# n => n  类似于: MySQL的窗口函数的效果

例子

# 需求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、分组+过滤

格式

对象.groupby('列名').filter(条件)
对象.query(条件)

例子

# 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的分组对象

格式

# 分组对象不能使用 0 索引获取数据,要获取数据, 可以通过  grouped.get_group() 函数实现
DataFrameGroupBy对象.get_group(('列名'))

例子

# 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、透视表

格式

# 参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)

例子

# 可视化上述的数据.
# 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中的日期类型

格式

对象.to_datetime('时间')# Timestamp('时间:年月日时分秒')
对象.Timestamp('时间')# Timestamp('时间:年月日时分秒')

例子

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、提取日期中的各个部分

格式

对象.to_datetime('时间')# Timestamp('时间:年月日时分秒')
对象.year # 年
对象.month # 月
对象.day # 日
对象.dayofyear # 一年的第几天

对象.Timestamp('时间')# Timestamp('时间:年月日时分秒')

例子

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、生成日期序列

格式

对象.date_range(start, end, freq='生成规则')# 包左包右

例子

# 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')
'''


原文地址:https://blog.csdn.net/egglikeFe/article/details/142469199

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!