前言
开刷Pandas数据分析,看起来很好理解,不过没做笔记没敲代码心里总是不安稳,所以复现下课程代码并演示其中遇到的问题,顺便水一水笔记好了
参考资料:
课程视频链接:Pandas数据分析从入门到实战
数据及代码示例:ant-learn-pandas: pandas学习课程代码仓库 (gitee.com)
一、数据读取
0. 数据类型
数据类型 | 说明 | Pandas读取方法 |
---|---|---|
csv, tsv, txt | 用逗号、tab或其它字符分割的文本文件 | read_csv |
excel | xls或xlsx文件 | read_excel |
mysql | 关系型数据表 | read_sql |
1. read_csv
1.1 读取csv文件
csv是以逗号分割的文本文件,如下:
userId,movieId,rating,timestamp
1,1,4.0,964982703
1,3,4.0,964981247
1,6,4.0,964982224
1,47,5.0,964983815
直接使用read_csv读取该文件
fpath = "../datas/ml-latest-small/ratings.csv"
# 读取
ratings = pd.read_csv(fpath)
1.2 指定分割符
已知access_pvuv.txt如下:
2019-09-10 139 92
2019-09-09 185 153
2019-09-08 123 59
2019-09-07 65 40
2019-09-06 157 98
2019-09-05 205 151
2019-09-04 196 167
2019-09-03 216 176
2019-09-02 227 148
2019-09-01 105 61
为read_csv添加参数delimiter (或seq) 指定分隔符,header=None表示没有第一行列名称
fpath = "../datas/crazyant/access_pvuv.txt"
pvuv = pd.read_csv(
fpath,
delimiter='\t',
header=None,
names=['date', 'pv', 'uv']
)
2. read_excel
fpath = "../datas/crazyant/access_pvuv.xlsx"
pvuv = pd.read_excel(fpath)
print(pvuv)
'''
输出结果
日期 PV UV
0 2019-09-10 139 92
1 2019-09-09 185 153
2 2019-09-08 123 59
3 2019-09-07 65 40
4 2019-09-06 157 98
5 2019-09-05 205 151
6 2019-09-04 196 167
7 2019-09-03 216 176
8 2019-09-02 227 148
9 2019-09-01 105 61
'''
3. read_sql
3.1 使用pymysql
连接数据库,选择编码方式
import pandas as pd
import pymysql
conn = pymysql.connect(
host='localhost',
user='root',
password='password',
database='dbname',
charset='utf8'
)
table = pd.read_sql("select * from tbname", con=conn)
print(table)
'''
输出结果
id preorder_traversal_string
0 1 4_2_1_0_#_#_#_3_#_#_8_7_#_#_11_#_#_
1 2 5_4_2_#_#_1_7_#_6_#_#_#_3_0_#_5_1_#_#_#_6_#_#_
2 3 6_#_7_5_3_#_#_1_#_#_2_8_#_#_#_
'''
但是会报警告:
UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.
这里建议我们使用SQLAlchemy
3.2 使用SQLAlchemy
import pandas as pd
from sqlalchemy import create_engine
host = "127.0.0.1"
user = "root"
password = "password"
database = "dbname"
engine = create_engine(f"mysql+pymysql://{user}:{password}@{host}/{database}")
sql = 'select * from tbname'
table = pd.read_sql(sql=sql, con=engine)
print(table)
'''
输出结果
id preorder_traversal_string
0 1 4_2_1_0_#_#_#_3_#_#_8_7_#_#_11_#_#_
1 2 5_4_2_#_#_1_7_#_6_#_#_#_3_0_#_5_1_#_#_#_6_#_#_
2 3 6_#_7_5_3_#_#_1_#_#_2_8_#_#_#_
'''
4. 查看数据格式
# 查看前几行数据,默认为5行
print(ratings.head())
'''
输出结果
userId movieId rating timestamp
0 1 1 4.0 964982703
1 1 3 4.0 964981247
2 1 6 4.0 964982224
3 1 47 5.0 964983815
4 1 50 5.0 964982931
'''
# 查看数据的形状,返回(行数、列数)
print(ratings.shape)
'''
输出结果
(100836, 4)
'''
# 查看列名列表
print(ratings.columns)
'''
输出结果
Index(['userId', 'movieId', 'rating', 'timestamp'], dtype='object')
'''
# 查看索引列
print(ratings.index)
'''
输出结果
RangeIndex(start=0, stop=100836, step=1)
'''
# 查看每列的数据类型
print(ratings.dtypes)
'''
输出结果
userId int64
movieId int64
rating float64
timestamp int64
dtype: object
'''
二、数据结构
1. Series
import pandas as pd
# 创建Series
s1 = pd.Series(list('abcd'))
print(s1)
'''
0 a
1 b
2 c
3 d
dtype: object
'''
# 指定索引创建Series
s2 = pd.Series(list('efgh'), index=list('abcd'))
print(s2)
print(s2.index)
'''
a e
b f
c g
d h
dtype: object
Index(['a', 'b', 'c', 'd'], dtype='object')
'''
# 字典创建Series
dict = {
'a': 'e',
'b': 'f',
'c': 'g',
'd': 'h'
}
s3 = pd.Series(dict)
print(s3)
'''
a e
b f
c g
d h
dtype: object
'''
2. DataFrame
import pandas as pd
# 字典创建DataFrame
data = {
'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9]
}
df = pd.DataFrame(data)
print(df)
'''
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
'''
# 输出DataFrame的索引、列标签以及数据类型
print(df.index, '\n\n', df.columns, '\n\n', df.dtypes)
'''
RangeIndex(start=0, stop=5, step=1)
Index(['state', 'year', 'pop'], dtype='object')
state object
year int64
pop float64
dtype: object
'''
三、查询数据
0. 查询方法
- df.loc :基于标签索引,结果包含最后一个标签的值
- df.iloc:基于位置索引,结果不包含最后一个位置的值
- df.where
- df.query
本节主要介绍df.loc
1. 数据预处理
import pandas as pd
# 数据预处理
df = pd.read_csv("../datas/beijing_tianqi/beijing_tianqi_2018.csv")
print(df.head())
'''
ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 良 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 优 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 优 1
3 2018-01-04 0℃ -8℃ 阴 东北风 1-2级 28 优 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 优
'''
# 设定索引为日期,方便按日期筛选
df.set_index('ymd', inplace=True)
# 替换掉温度的后缀℃
df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃", "").astype('int32')
df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃", "").astype('int32')
# df[df.columns["bWendu"]] = df["bWendu"].str.replace("℃", "").astype('int32')
# df[df.columns["yWendu"]] = df["yWendu"].str.replace("℃", "").astype('int32')
print(df.head())
'''
bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-01-01 3 -6 晴~多云 东北风 1-2级 59 良 2
2018-01-02 2 -5 阴~多云 东北风 1-2级 49 优 1
2018-01-03 2 -5 多云 北风 1-2级 28 优 1
2018-01-04 0 -8 阴 东北风 1-2级 28 优 1
2018-01-05 3 -6 多云~晴 西北风 1-2级 50 优
'''
这里会报一个警告:
DeprecationWarning: In a future version, `df.iloc[:, i] = newvals` will attempt to set the
values inplace instead of always setting a new array. To retain the old behavior, use either
`df[df.columns[i]] = newvals` or, if columns are non-unique, `df.isetitem(i, newvals)`
2. 按数值、列表、区间查询
# 得到单个值
single_value = df.loc['2018-01-03', 'bWendu']
# 得到一列/一行
s1 = df.loc['2018-01-03', ['bWendu', 'yWendu']]
s2 = df.loc[['2018-01-03', '2018-01-04', '2018-01-05'], 'bWendu']
# 得到DataFrame
df2 = df.loc[['2018-01-03','2018-01-04','2018-01-05'], ['bWendu', 'yWendu']]
# 按区间查询
df3 = df.loc['2018-01-03':'2018-01-05', 'bWendu':'fengxiang']
3. 条件查询
# 查询最高温度小于30度,并且最低温度大于15度,并且是晴天,并且天气为优的数据
df4 = df.loc[(df["bWendu"] <= 30) & (df["yWendu"] >= 15)
& (df["tianqi"] == '晴') & (df["aqiLevel"] == 1), :]
print(df4)
'''
bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-08-24 30 20 晴 北风 1-2级 40 优 1
2018-09-07 27 16 晴 西北风 3-4级 22 优 1
'''
其中,条件表达式返回的是一个布尔值的Series
# 观察条件表达式
print(df["yWendu"] < -10)
'''
2018-01-01 False
2018-01-02 False
2018-01-03 False
2018-01-04 False
2018-01-05 False
...
2018-12-27 True
2018-12-28 True
2018-12-29 True
2018-12-30 True
2018-12-31 False
Name: yWendu, Length: 365, dtype: bool
'''
4. 函数查询
# 直接写lambda表达式
df5 = df.loc[lambda df : (df["bWendu"] <= 30) & (df["yWendu"] >= 15), :]
# 编写自己的函数,查询9月份,空气质量好的数据
def query_my_data(df):
return df.index.str.startswith("2018-09") & (df["aqiLevel"] == 1)
df6 = df.loc[query_my_data, :]
四、新增数据列
1. 直接赋值
df.loc[:, 'wencha'] = df['bWendu'] - df['yWendu']
2. df.apply
传入一个函数并选定axis:
- 当axis=1,函数的参数为一行的Series(常用)
- 当axis=0,函数的参数为一列的Series
def get_wendu_type(x):
if x["bWendu"] > 33:
return '高温'
if x["yWendu"] < -10:
return '低温'
return '常温'
df.loc[:, "wendu_type"] = df.apply(get_wendu_type, axis=1)
3. df.assign
df.assign总是会创建一个新的copy
利用lambda表达式,处理原来的数据得到新列
# 可以同时添加多个新的列
df.assign(
yWendu_huashi = lambda x : x["yWendu"] * 9 / 5 + 32,
# 摄氏度转华氏度
bWendu_huashi = lambda x : x["bWendu"] * 9 / 5 + 32
)
4. 条件选择分组后赋值
# 先创建空列(这是第一种创建新列的方法)
df['wencha_type'] = ''
错误示例:
df.loc[df["bWendu"]-df["yWendu"]>10]["wencha_type"] = "温差大"
df.loc[df["bWendu"]-df["yWendu"]<=10]["wencha_type"] = "温差正常"
两个[]的链式操作相当于
df.get(condition).set(wen_cha)
这里get得到的结果可能是view也可能是copy,存在歧义
正确示范:
df.loc[df["bWendu"]-df["yWendu"]>10, "wencha_type"] = "温差大"
df.loc[df["bWendu"]-df["yWendu"]<=10, "wencha_type"] = "温差正常"
五、聚合查询
1. describe输出统计结果
# 一下子提取所有数字列统计结果
df.describe()
bWendu yWendu aqi aqiLevel wencha
count 365.000000 365.000000 365.000000 365.000000 365.000000
mean 18.665753 8.358904 82.183562 2.090411 10.306849
std 11.858046 11.755053 51.936159 1.029798 2.781233
min -5.000000 -12.000000 21.000000 1.000000 2.000000
25% 8.000000 -3.000000 46.000000 1.000000 8.000000
50% 21.000000 8.000000 69.000000 2.000000 10.000000
75% 29.000000 19.000000 104.000000 3.000000 12.000000
max 38.000000 27.000000 387.000000 6.000000 18.000000
describe只能得到数值列的统计结果
2. 非数值列统计
2.1 unique唯一去重
print(df['tianqi'].unique())
['晴~多云' '阴~多云' '多云' '阴' '多云~晴' '多云~阴' '晴' '阴~小雪' '小雪~多云' '小雨~ 阴' '小雨~雨夹雪'
'多云~小雨' '小雨~多云' '大雨~小雨' '小雨' '阴~小雨' '多云~雷阵雨' '雷阵雨~多云' '阴~ 雷阵雨' '雷阵雨'
'雷阵雨~大雨' '中雨~雷阵雨' '小雨~大雨' '暴雨~雷阵雨' '雷阵雨~中雨' '小雨~雷阵雨' '雷 阵雨~阴' '中雨~小雨'
'小雨~中雨' '雾~多云' '霾']
2.2 value_counts按值计数
print(df['wencha_type'].value_counts())
温差正常 187
温差大 178
Name: wencha_type, dtype: int64
3. 协方差和相关系数
print(df.cov(), '\n\n', df.corr())
bWendu yWendu aqi aqiLevel wencha
bWendu 140.613247 135.529633 47.462622 0.879204 5.083614
yWendu 135.529633 138.181274 16.186685 0.264165 -2.651641
aqi 47.462622 16.186685 2697.364564 50.749842 31.275937
aqiLevel 0.879204 0.264165 50.749842 1.060485 0.615038
wencha 5.083614 -2.651641 31.275937 0.615038 7.735255
bWendu yWendu aqi aqiLevel wencha
bWendu 1.000000 0.972292 0.077067 0.071999 0.154142
yWendu 0.972292 1.000000 0.026513 0.021822 -0.081106
aqi 0.077067 0.026513 1.000000 0.948883 0.216523
aqiLevel 0.071999 0.021822 0.948883 1.000000 0.214740
wencha 0.154142 -0.081106 0.216523 0.214740 1.000000
六、缺失值处理
1. 处理方式
Pandas使用这些函数处理缺失值:
- isnull和notnull:检测是否是空值,可用于df和series
- dropna:丢弃、删除缺失值
- axis : 删除行还是列,{0 or ‘index’, 1 or ‘columns’}, default 0
- how : 如果等于any则任何值为空都删除,如果等于all则所有值都为空才删除
- inplace : 如果为True则修改当前df,否则返回新的df
- fillna:填充空值
- value:用于填充的值,可以是单个值,或者字典(key是列名,value是值)
- method : 等于ffill使用前一个不为空的值填充forword fill;等于bfill使用后一个不为空的值填充backword fill
- axis : 按行还是列填充,{0 or ‘index’, 1 or ‘columns’}
- inplace : 如果为True则修改当前df,否则返回新的df
2. 数据清洗示例
2.1 检测空值
# 跳过前面两空行
studf = pd.read_excel("../datas/student_excel/student_excel.xlsx", skiprows=2)
# 检测空值
print(studf.isnull())
Unnamed: 0 姓名 科目 分数
0 True False False False
1 True True False False
2 True True False False
3 True True True True
4 True False False False
5 True True False True
6 True True False False
7 True True True True
8 True False False False
9 True True False False
10 True True False False
2.2 dropna示例
# 删除全为空的行和列
studf.dropna(axis=1, how='all', inplace=True)
studf.dropna(axis=0, how='all', inplace=True)
print(studf)
姓名 科目 分数
0 小明 语文 85.0
1 NaN 数学 80.0
2 NaN 英语 90.0
4 小王 语文 85.0
5 NaN 数学 NaN
6 NaN 英语 90.0
8 小刚 语文 85.0
9 NaN 数学 80.0
10 NaN 英语 90.0
2.3 fillna示例
# 将空的分数填充为0
# 将空的姓名填充为上一个值
studf['分数'].fillna(value=0, inplace=True)
studf['姓名'].fillna(method='ffill', inplace=True)
print(studf)
姓名 科目 分数
0 小明 语文 85.0
1 小明 数学 80.0
2 小明 英语 90.0
4 小王 语文 85.0
5 小王 数学 0.0
6 小王 英语 90.0
8 小刚 语文 85.0
9 小刚 数学 80.0
10 小刚 英语 90.0
七、数据排序
1. 排序方法
- Series的排序:
Series.sort_values(ascending=True, inplace=False)
参数说明:
- ascending:默认为True升序排序,为False降序排序
- inplace:是否修改原始Series
- DataFrame的排序:
DataFrame.sort_values(by, ascending=True, inplace=False)
参数说明:
- by:字符串或者List<字符串>,单列排序或者多列排序
- ascending:bool或者bool的列表,升序还是降序,如果是list对应by的多列
- inplace:是否修改原始DataFrame
2. 排序示例
# Series的排序
print(df['aqi'].sort_values())
ymd
2018-09-29 21
2018-10-09 21
2018-09-07 22
2018-09-30 22
2018-10-29 22
...
2018-11-14 266
2018-03-13 287
2018-04-02 287
2018-03-14 293
2018-03-28 387
Name: aqi, Length: 365, dtype: int64
# DataFrame的排序
df.sort_values(by=['aqi', 'bWendu'], ascending=[False, True], inplace=True)
print(df[['aqi', 'bWendu']])
aqi bWendu
ymd
2018-03-28 387 25
2018-03-14 293 15
2018-03-13 287 17
2018-04-02 287 26
2018-11-14 266 13
... ... ...
2018-10-29 22 15
2018-09-30 22 19
2018-09-07 22 27
2018-10-09 21 15
2018-09-29 21 22
八、字符串处理
0. Pandas的str
Pandas的字符串处理:
- 使用方法:先获取Series的str属性,然后在属性上调用函数;
- 只能在字符串列上使用,不能数字列上使用;
- Dataframe上没有str属性和处理方法
- Series.str并不是Python原生字符串,而是自己的一套方法,不过大部分和原生str很相似;
1. 基础用法
获取str属性,并调用各种方法,如replace, isnumeric, len
print(df['wencha_type'].str.len())
ymd
2018-03-28 3
2018-03-14 4
2018-03-13 3
2018-04-02 3
2018-11-14 4
..
2018-10-29 3
2018-09-30 4
2018-09-07 3
2018-10-09 3
2018-09-29 3
Name: wencha_type, Length: 365, dtype: int64
2. 条件查询
或使用contains, startswith等得到bool的Series做条件查询
print(df.loc[df['tianqi'].str.startswith('多云'), ['tianqi', 'fengxiang']])
tianqi fengxiang
ymd
2018-03-28 多云~晴 东风
2018-03-14 多云~阴 东北风
2018-04-02 多云 北风
2018-11-14 多云 南风
2018-11-26 多云 东南风
... ... ...
2018-01-25 多云 东北风
2018-10-10 多云~晴 西北风
2018-02-03 多云 北风
2018-09-30 多云 西北风
2018-10-09 多云~晴 西北风
3. 正则表达式
由于Series.str天然支持正则表达式,示例如下:
匹配字符集合并做替换:
# 添加新列
def get_nianyueri(x):
year,month,day = x["ymd"].split("-")
return f"{year}年{month}月{day}日"
df["中文日期"] = df.apply(get_nianyueri, axis=1)
# 尝试将 年 月 日 去除
df.loc[:, '中文日期'] = df['中文日期'].str.replace('[年月日]', '')
print(df['中文日期'])
86 20180328
72 20180314
71 20180313
91 20180402
317 20181114
...
301 20181029
272 20180930
249 20180907
281 20181009
271 20180929
Name: 中文日期, Length: 365, dtype: object
捕获组提取数据:
extracted_fengli = df['fengli'].str.extract(r'(\d)-(\d)')
print(extracted_fengli.head())
0 1
86 1 2
72 1 2
71 1 2
91 1 2
317 1 2
.. .. ..
301 3 4
272 4 5
249 3 4
281 4 5
271 3 4
九、索引
0. 索引的作用
选择恰当的索引可以加速查询性能
- 当索引是唯一的时,Pandas会用哈希表优化性能,时间复杂度为O(1)
- 当索引不唯一,但是单调时,Pandas会使用二分查找,时间复杂度为O(log n)
- 当索引既不唯一且不单调时,Pandas只能遍历,时间复杂度为O(n)
因此,我们要判断当前索引是否为以上类型,尽可能选择唯一的索引,单调次之
1. 选择索引示例
原始数据如下:
print(df.head())
userId movieId rating timestamp
0 1 1 4.0 964982703
1 1 3 4.0 964981247
2 1 6 4.0 964982224
3 1 47 5.0 964983815
4 1 50 5.0 964982931
判断每一列是否存在唯一约束:
print(df.nunique() == len(df))
userId False
movieId False
rating False
timestamp False
dtype: bool
判断每一列是否单调:
# 使用这一句会报FutureWarning
# is_monotonic = df.apply(lambda x: x.is_monotonic)
is_monotonic_increasing = df.apply(lambda x: x.is_monotonic_increasing)
is_monotonic_decreasing = df.apply(lambda x: x.is_monotonic_decreasing)
print(is_monotonic_increasing, '\n\n', is_monotonic_decreasing)
userId True
movieId False
rating False
timestamp False
dtype: bool
userId False
movieId False
rating False
timestamp False
dtype: bool
2. 设置索引示例
DataFrame.set_index(keys, append=False, drop=True, inplace=False)
keys代表被用作索引的列
append代表是否保留原来的索引
drop表示是否将指定的列在原数据列中删除
inplace表示是否在原数据上修改
df.set_index('userId',append=True, drop=False, inplace=True)
print(df.head())
userId movieId rating timestamp
userId
0 1 1 1 4.0 964982703
1 1 1 3 4.0 964981247
2 1 1 6 4.0 964982224
3 1 1 47 5.0 964983815
4 1 1 50 5.0 964982931