前言
开刷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']
)
分割符也可以是多个字符,但是seq指定多个字符默认会被当作正则表达式,需指定engine='python'
df_ratings = pd.read_csv(
"../datas/movielens-1m/ratings.dat",
sep="::",
engine='python',
names="UserID::MovieID::Rating::Timestamp".split("::")
)
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. Series.map
传入一个字典,将Series的值根据字典映射
示例如下:
原始数据
stocks = pd.read_excel('../datas/stocks/互联网公司股票.xlsx')
print(stocks.head(), '\n', stocks['公司'].unique())
日期 公司 收盘 开盘 高 低 交易量 涨跌幅
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00
['BIDU' 'BABA' 'IQ' 'JD']
现将 公司 这一列通过字典对应到该公司的中文名
dict_company_names = {
"bidu": "百度",
"baba": "阿里巴巴",
"iq": "爱奇艺",
"jd": "京东"
}
stocks['公司中文'] = stocks['公司'].str.lower().map(dict_company_names)
print(stocks['公司中文'].unique())
['百度' '阿里巴巴' '爱奇艺' '京东']
其实map也可以像apply一样传一个函数,修改Series的每个值
5. 条件选择分组后赋值
# 先创建空列(这是第一种创建新列的方法)
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
4. 分组后聚合查询
4.1 pd.groupby
DataFrame.groupby(by, axis=0, as_index=True, sort=True, group_keys=True, dropna=True)
- by: 字符串或者List<字符串>,用于指定分组
- axis:默认为0,对行做分组
- as_index:将分组的keys作为索引值,默认为True
- sort:将结果按分组的keys排序,默认为True
- group_keys:与as_index相同,但是只有在使用apply时生效,在使用聚合时失效(都为True)
DataFrame.groupby的返回值是一个DataFrameGroupBy对象
4.2 聚合查询示例
1) 使用已有的聚合函数
df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],
'B': ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],
'C': np.random.randn(8),
'D': np.random.randn(8)})
A B C D
0 foo one 0.411257 -1.345015
1 bar one 0.680037 0.225904
2 foo two -0.640561 -0.447943
3 bar three -0.354500 -0.517335
4 foo two 1.092376 -0.978323
agg列表传参,对不同列使用相同聚合函数:
df_group1 = df.groupby(['A', 'B']).agg([np.sum, np.mean])
C D
sum mean sum mean
A B
bar one 0.680037 0.680037 0.225904 0.225904
three -0.354500 -0.354500 -0.517335 -0.517335
two -1.047294 -1.047294 -1.879435 -1.879435
foo one 0.419079 0.209540 -1.893515 -0.946758
three -0.681836 -0.681836 1.008956 1.008956
two 0.451816 0.225908 -1.426266 -0.713133
agg对象传参,对不同列使用不同聚合函数:
df_group2 = df.groupby(['A', 'B']).agg({'C': np.sum, 'D': np.mean})
C D
A B
bar one 0.680037 0.225904
three -0.354500 -0.517335
two -1.047294 -1.879435
foo one 0.419079 -0.946758
three -0.681836 1.008956
two 0.451816 -0.713133
DataFrameGroupBy对象原生的函数:
df_group3 = df.groupby('A')['C'].mean()
A
bar -0.240586
foo 0.037812
Name: C, dtype: float64
2)自定义聚合函数
DataFrameGroupBy.apply(func, *args, **kwargs)
- func:自定义的聚合函数,第一个参数是一个DataFrame
- *args, **kwargs:func第二个及往后的参数(如有)
使用的数据为多次出现在例子中的天气数据,增加一列为月份,由ymd年月日得到:
# 新增一列为月份
df['month'] = df['ymd'].str[:7]
ymd bWendu yWendu tianqi ... aqi aqiInfo aqiLevel month
0 2018-01-01 3 -6 晴~多云 ... 59 良 2 2018-01
1 2018-01-02 2 -5 阴~多云 ... 49 优 1 2018-01
2 2018-01-03 2 -5 多云 ... 28 优 1 2018-01
3 2018-01-04 0 -8 阴 ... 28 优 1 2018-01
4 2018-01-05 3 -6 多云~晴 ... 50 优 1 2018-01
对月份做分组,并对分组后的数据对最高温度做排序,返回最高温度最大的3行
def getTopN(df, n):
return df.sort_values(by='bWendu')[['ymd', 'bWendu', 'yWendu']][-n:]
df_group = df.groupby('month').apply(getTopN, 3)
ymd bWendu yWendu
month
2018-01 16 2018-01-17 6 -7
13 2018-01-14 6 -5
18 2018-01-19 7 -4
2018-02 58 2018-02-28 9 -2
53 2018-02-23 10 -4
六、缺失值处理
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
十、数据合并
1. merge合并
1.1 函数定义
课件中给出的pd.merge的解释如下:
pd.merge(left, right, how=‘inner’, on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=True,
suffixes=(‘_x’, ‘_y’), copy=True, indicator=False,
validate=None)
- left,right:要merge的dataframe或者有name的Series
- how:join类型,‘left’, ‘right’, ‘outer’, ‘inner’
- on:join的key,left和right都需要有这个key
- left_on:left的df或者series的key
- right_on:right的df或者seires的key
- left_index,right_index:使用index而不是普通的column做join
- suffixes:两个元素的后缀,如果列有重名,自动添加后缀,默认是(‘_x’, ‘_y’)
如果学过SQL语句,可以很好地将两者对应起来:
pd.merge(left, right, on='key')
相当于
FROM left JOIN right ON left.key=right.key
pd.merge(left, right, left_on='key1', right_on='key2')
相当于
FROM left JOIN right ON left.key1=right.key2
而how='left'/'right'/'outer'就相当于LEFT/RIGHT/FULL JOIN
1.2 合并示例
df_ratings = pd.read_csv(
"../datas/movielens-1m/ratings.dat",
sep="::",
engine='python',
names="UserID::MovieID::Rating::Timestamp".split("::")
)
df_users = pd.read_csv(
"../datas/movielens-1m/users.dat",
sep="::",
engine='python',
names="UserID::Gender::Age::Occupation::Zip-code".split("::")
)
print(df_ratings.head(), '\n', df_users.head())
df_joined1 = pd.merge(df_ratings, df_users, on='UserID')
print(df_joined1.head())
输出结果如下:
UserID MovieID Rating Timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291
UserID Gender Age Occupation Zip-code
0 1 F 1 10 48067
1 2 M 56 16 70072
2 3 M 25 15 55117
3 4 M 45 7 02460
4 5 M 25 20 55455
UserID MovieID Rating Timestamp Gender Age Occupation Zip-code
0 1 1193 5 978300760 F 1 10 48067
1 1 661 3 978302109 F 1 10 48067
2 1 914 3 978301968 F 1 10 48067
3 1 3408 4 978300275 F 1 10 48067
4 1 2355 5 978824291 F 1 10 48067
2. concat合并
2.1 函数定义
pandas.concat(objs, axis=0, join=‘outer’, ignore_index=False)
- objs:一个列表,内容可以是DataFrame或者Series,可以混合
- axis:默认是0代表按行合并,如果等于1代表按列合并
- join:合并的时候索引的对齐方式,默认是outer join,也可以是inner join
- ignore_index:是否忽略掉原来的数据索引
DataFrame.append(other, ignore_index=False)
append只有按行合并,没有按列合并,相当于concat按行的简写形式
- other:单个dataframe、series、dict,或者列表
- ignore_index:是否忽略掉原来的数据索引
2.2 合并示例
原始数据:
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3'],
'E': ['E0', 'E1', 'E2', 'E3']
})
df2 = pd.DataFrame({ 'A': ['A4', 'A5', 'A6', 'A7'],
'B': ['B4', 'B5', 'B6', 'B7'],
'C': ['C4', 'C5', 'C6', 'C7'],
'D': ['D4', 'D5', 'D6', 'D7'],
'F': ['F4', 'F5', 'F6', 'F7']
})
print(df1, '\n', df2)
A B C D E
0 A0 B0 C0 D0 E0
1 A1 B1 C1 D1 E1
2 A2 B2 C2 D2 E2
3 A3 B3 C3 D3 E3
A B C D F
0 A4 B4 C4 D4 F4
1 A5 B5 C5 D5 F5
2 A6 B6 C6 D6 F6
3 A7 B7 C7 D7 F7
有无ignore_index的区别:
df3 = pd.concat([df1, df2])
df4 = pd.concat([df1, df2], ignore_index=True)
print(df3, '\n', df4)
A B C D E F
0 A0 B0 C0 D0 E0 NaN
1 A1 B1 C1 D1 E1 NaN
2 A2 B2 C2 D2 E2 NaN
3 A3 B3 C3 D3 E3 NaN
0 A4 B4 C4 D4 NaN F4
1 A5 B5 C5 D5 NaN F5
2 A6 B6 C6 D6 NaN F6
3 A7 B7 C7 D7 NaN F7
A B C D E F
0 A0 B0 C0 D0 E0 NaN
1 A1 B1 C1 D1 E1 NaN
2 A2 B2 C2 D2 E2 NaN
3 A3 B3 C3 D3 E3 NaN
4 A4 B4 C4 D4 NaN F4
5 A5 B5 C5 D5 NaN F5
6 A6 B6 C6 D6 NaN F6
7 A7 B7 C7 D7 NaN F7
默认为outer,使用inner如下:
df5 = pd.concat([df1, df2], join='inner')
print(df5)
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
0 A4 B4 C4 D4
1 A5 B5 C5 D5
2 A6 B6 C6 D6
3 A7 B7 C7 D7
十一、数据透视
1. 透视方法
1) stack 和 unstack
stack和unstack互为逆操作
stack将列索引转成最内层行索引
而unstack将最内层行索引转成列索引
具体参考:pandas中DataFrame的stack()、unstack()和pivot()方法的对比_pandas pivot和stack区别-CSDN博客
2) reset_index 和 pivot
DataFrame.pivot(index, columns, values)
指定三列,依次为行索引、列索引、构建一个DataFrame
使用rest_index可实现逆操作
2. 透视示例
2.1 数据预处理
原始数据:
df = pd.read_csv(
"../datas/movielens-1m/ratings.dat",
header=None,
names="UserID::MovieID::Rating::Timestamp".split("::"),
sep="::",
engine="python"
)
print(df.head())
UserID MovieID Rating Timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291
调用pd.to_datetime将时间戳转化为时间对象,通过访问对象的属性dt.month访问月份,并做统计操作
# 统计每个月份每个评级的用户数量
df["pdate"] = pd.to_datetime(df["Timestamp"], unit='s')
df_group = df.groupby([df['pdate'].dt.month, 'Rating'])['UserID'].count()
print(df_group.head())
pdate Rating
1 1 1127
2 2608
3 6442
4 8400
5 4495
Name: UserID, dtype: int64
2.2 使用透视
1) unstack 和 stack示例
df_unstack = df_group.unstack()
print(df_unstack.head())
Rating 1 2 3 4 5
pdate
1 1127 2608 6442 8400 4495
2 629 1464 3297 4403 2335
3 466 1077 2523 3032 1439
4 1048 2247 5501 6748 3863
5 4557 7631 18481 25769 17840
绘制曲线
df_unstack.plot()
plt.show()
2) reset_index 和 pivot示例
df_reset = df_group.reset_index()
print(df_reset.head())
pdate Rating pv
0 1 1 1127
1 1 2 2608
2 1 3 6442
3 1 4 8400
4 1 5 4495
df_pivot = df_reset.pivot('pdate', 'Rating', 'pv')
print(df_pivot.head())
Rating 1 2 3 4 5
pdate
1 1127 2608 6442 8400 4495
2 629 1464 3297 4403 2335
3 466 1077 2523 3032 1439
4 1048 2247 5501 6748 3863
5 4557 7631 18481 25769 17840