代码:
scores = np.random.randint(60, 101, (5, 3))
courses = ['语文', '数学', '英语']
ids = [1001, 1002, 1003, 1004, 1005]
df1 = pd.DataFrame(data=scores, columns=courses, index=ids)
df1
输出:
语文 数学 英语
1001 69 80 79
1002 71 60 100
1003 94 81 93
1004 88 88 67
1005 82 66 60
代码:
scores = {
'语文': [62, 72, 93, 88, 93],
'数学': [95, 65, 86, 66, 87],
'英语': [66, 75, 82, 69, 82],
}
ids = [1001, 1002, 1003, 1004, 1005]
df2 = pd.DataFrame(data=scores, index=ids)
df2
输出:
语文 数学 英语
1001 69 80 79
1002 71 60 100
1003 94 81 93
1004 88 88 67
1005 82 66 60
可以通过pandas
模块的read_csv
函数来读取 CSV 文件,read_csv
函数的参数非常多,下面接受几个比较重要的参数。
sep
/delimiter
:分隔符,默认是,
。header
:表头(列索引)的位置,默认值是infer
,用第一行的内容作为表头(列索引)。index_col
:用作行索引(标签)的列。usecols
:需要加载的列,可以使用序号或者列名。true_values
/false_values
:哪些值被视为布尔值True
/False
。skiprows
:通过行号、索引或函数指定需要跳过的行。skipfooter
:要跳过的末尾行数。nrows
:需要读取的行数。na_values
:哪些值被视为空值。
代码:
df3 = pd.read_csv('2018年北京积分落户数据.csv', index_col='id')
df3
输出:
name birthday company score
id
1 杨x 1972-12 北京利德xxxx 122.59
2 纪x 1974-12 北京航天xxxx 121.25
3 王x 1974-05 品牌联盟xxxx 118.96
4 杨x 1975-07 中科专利xxxx 118.21
5 张x 1974-11 北京阿里xxxx 117.79
... ... ... ... ...
6015 孙x 1978-08 华为海洋xxxx 90.75
6016 刘x 1976-11 福斯流体xxxx 90.75
6017 周x 1977-10 赢创德固xxxx 90.75
6018 赵x 1979-07 澳科利耳xxxx 90.75
6019 贺x 1981-06 北京宝洁xxxx 90.75
6019 rows × 4 columns
说明:如果需要上面例子中的 CSV 文件,可以通过下面的百度云盘地址进行获取,数据在《从零开始学数据分析》目录中。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取码:e7b4。
可以通过pandas
模块的read_excel
函数来读取 Exce l文件,该函数与上面的read_csv
非常相近,多了一个sheet_name
参数来指定数据表的名称,但是不同于 CSV 文件,没有sep
或delimiter
这样的参数。下面的代码中,read_excel
函数的skiprows
参数是一个 Lambda 函数,通过该 Lambda 函数指定只读取 Excel 文件的表头和其中10%的数据,跳过其他的数据。
代码:
import random
df4 = pd.read_excel(
io='小宝剑大药房2018年销售数据.xlsx',
usecols=['购药时间', '社保卡号', '商品名称', '销售数量', '应收金额', '实收金额'],
skiprows=lambda x: x > 0 and random.random() > 0.1
)
df4
说明:如果需要上面例子中的 Excel 文件,可以通过下面的百度云盘地址进行获取,数据在《从零开始学数据分析》目录中。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取码:e7b4。
输出:
购药时间 社保卡号 商品名称 销售数量 应收金额 实收金额
0 2018-03-23 星期三 10012157328 强力xx片 1 13.8 13.80
1 2018-07-12 星期二 108207828 强力xx片 1 13.8 13.80
2 2018-01-17 星期日 13358228 清热xx液 1 28.0 28.00
3 2018-07-11 星期一 10031402228 三九xx灵 5 149.0 130.00
4 2018-01-20 星期三 10013340328 三九xx灵 3 84.0 73.92
... ... ... ... ... ... ...
618 2018-03-05 星期六 10066059228 开博xx通 2 56.0 49.28
619 2018-03-22 星期二 10035514928 开博xx通 1 28.0 25.00
620 2018-04-15 星期五 1006668328 开博xx通 2 56.0 50.00
621 2018-04-24 星期日 10073294128 高特xx灵 1 5.6 5.60
622 2018-04-24 星期日 10073294128 高特xx灵 10 56.0 56.0
623 rows × 6 columns
pandas
模块的read_sql
函数可以通过 SQL 语句从数据库中读取数据创建DataFrame
对象,该函数的第二个参数代表了需要连接的数据库。对于 MySQL 数据库,我们可以通过pymysql
或mysqlclient
来创建数据库连接,得到一个Connection
对象,而这个对象就是read_sql
函数需要的第二个参数,代码如下所示。
代码:
import pymysql
# 创建一个MySQL数据库的连接对象
conn = pymysql.connect(
host='47.104.31.138', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
# 通过SQL从数据库读取数据创建DataFrame
df5 = pd.read_sql('select * from tb_emp', conn, index_col='eno')
df5
提示:执行上面的代码需要先安装
pymysql
库,如果尚未安装,可以先在 Notebook 的单元格中先执行!pip install pymysql
,然后再运行上面的代码。上面的代码连接的是我部署在阿里云上的 MySQL 数据库,公网 IP 地址:47.104.31.138
,用户名:guest
,密码:Guest.618
,数据库:hrs
,表名:tb_emp
,字符集:utf8mb4
,大家可以使用这个数据库,但是不要进行恶意的访问。
输出:
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 NaN 10
3588 朱九真 会计 5566.0 2500 NaN 10
4466 苗人凤 销售员 3344.0 2500 NaN 30
5234 郭靖 出纳 5566.0 2000 NaN 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
在开始讲解DataFrame
的属性和方法前,我们先从之前提到的hrs
数据库中读取三张表的数据,创建出三个DataFrame
对象,代码如下所示。
import pymysql
conn = pymysql.connect(
host='47.104.31.138', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
dept_df = pd.read_sql('select * from tb_dept', conn, index_col='dno')
emp_df = pd.read_sql('select * from tb_emp', conn, index_col='eno')
emp2_df = pd.read_sql('select * from tb_emp2', conn, index_col='eno')
得到的三个DataFrame
对象如下所示。
部门表(dept_df
),其中dno
是部门的编号,dname
和dloc
分别是部门的名称和所在地。
dname dloc
dno
10 会计部 北京
20 研发部 成都
30 销售部 重庆
40 运维部 天津
员工表(emp_df
),其中eno
是员工编号,ename
、job
、mgr
、sal
、comm
和dno
分别代表员工的姓名、职位、主管编号、月薪、补贴和部门编号。
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 NaN 10
3588 朱九真 会计 5566.0 2500 NaN 10
4466 苗人凤 销售员 3344.0 2500 NaN 30
5234 郭靖 出纳 5566.0 2000 NaN 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
说明:在数据库中
mgr
和comm
两个列的数据类型是int
,但是因为有缺失值(空值),读取到DataFrame
之后,列的数据类型变成了float
,因为我们通常会用float
类型的NaN
来表示空值。
员工表(emp2_df
),跟上面的员工表结构相同,但是保存了不同的员工数据。
ename job mgr sal comm dno
eno
9800 骆昊 架构师 7800 30000 5000 20
9900 王小刀 程序员 9800 10000 1200 20
9700 王大锤 程序员 9800 8000 600 20
DataFrame
对象的属性如下表所示。
属性名 | 说明 |
---|---|
at / iat |
通过标签获取DataFrame 中的单个值。 |
columns |
DataFrame 对象列的索引 |
dtypes |
DataFrame 对象每一列的数据类型 |
empty |
DataFrame 对象是否为空 |
loc / iloc |
通过标签获取DataFrame 中的一组值。 |
ndim |
DataFrame 对象的维度 |
shape |
DataFrame 对象的形状(行数和列数) |
size |
DataFrame 对象中元素的个数 |
values |
DataFrame 对象的数据对应的二维数组 |
关于DataFrame
的方法,首先需要了解的是info()
方法,它可以帮助我们了解DataFrame
的相关信息,如下所示。
代码:
emp_df.info()
输出:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 14 entries, 1359 to 7800
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ename 14 non-null object
1 job 14 non-null object
2 mgr 13 non-null float64
3 sal 14 non-null int64
4 comm 6 non-null float64
5 dno 14 non-null int64
dtypes: float64(2), int64(2), object(2)
memory usage: 1.3+ KB
如果需要查看DataFrame
的头部或尾部的数据,可以使用head()
或tail()
方法,这两个方法的默认参数是5
,表示获取DataFrame
最前面5行或最后面5行的数据,如下所示。
emp_df.head()
输出:
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344 1800 200 30
2056 乔峰 分析师 7800 5000 1500 20
3088 李莫愁 设计师 2056 3500 800 20
3211 张无忌 程序员 2056 3200 NaN 20
3233 丘处机 程序员 2056 3400 NaN 20
如果要获取DataFrame
的某一列,例如取出上面emp_df
的ename
列,可以使用下面的两种方式。
emp_df.ename
或者
emp_df['ename']
执行上面的代码可以发现,我们获得的是一个Series
对象。事实上,DataFrame
对象就是将多个Series
对象组合到一起的结果。
如果要获取DataFrame
的某一行,可以使用整数索引或我们设置的索引,例如取出员工编号为2056
的员工数据,代码如下所示。
emp_df.iloc[1]
或者
emp_df.loc[2056]
通过执行上面的代码我们发现,单独取DataFrame
的某一行或某一列得到的都是Series
对象。我们当然也可以通过花式索引来获取多个行或多个列的数据,花式索引的结果仍然是一个DataFrame
对象。
获取多个列:
emp_df[['ename', 'job']]
获取多个行:
emp_df.loc[[2056, 7800, 3344]]
如果要获取或修改DataFrame
对象某个单元格的数据,需要同时指定行和列的索引,例如要获取员工编号为2056
的员工的职位信息,代码如下所示。
emp_df['job'][2056]
或者
emp_df.loc[2056]['job']
或者
emp_df.loc[2056, 'job']
我们推荐大家使用第三种做法,因为它只做了一次索引运算。如果要将该员工的职位修改为“架构师”,可以使用下面的代码。
emp_df.loc[2056, 'job'] = '架构师'
当然,我们也可以通过切片操作来获取多行多列,相信大家一定已经想到了这一点。
emp_df.loc[2056:3344]
输出:
ename job mgr sal comm dno
eno
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
上面我们提到了花式索引,相信大家已经联想到了布尔索引。跟ndarray
和Series
一样,我们可以通过布尔索引对DataFrame
对象进行数据筛选,例如我们要从emp_df
中筛选出月薪超过3500
的员工,代码如下所示。
emp_df[emp_df.sal > 3500]
输出:
ename job mgr sal comm dno
eno
2056 乔峰 分析师 7800.0 5000 1500.0 20
3251 张翠山 程序员 2056.0 4000 NaN 20
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
当然,我们也可以组合多个条件来进行数据筛选,例如从emp_df
中筛选出月薪超过3500
且部门编号为20
的员工,代码如下所示。
emp_df[(emp_df.sal > 3500) & (emp_df.dno == 20)]
输出:
ename job mgr sal comm dno
eno
2056 乔峰 分析师 7800.0 5000 1500.0 20
3251 张翠山 程序员 2056.0 4000 NaN 20
7800 张三丰 总裁 NaN 9000 1200.0 20
除了使用布尔索引,DataFrame
对象的query
方法也可以实现数据筛选,query
方法的参数是一个字符串,它代表了筛选数据使用的表达式,而且更符合 Python 程序员的使用习惯。下面我们使用query
方法将上面的效果重新实现一遍,代码如下所示。
emp_df.query('sal > 3500 and dno == 20')
有的时候,我们做数据分析需要的原始数据可能并不是来自一个地方,就像上面的例子中,我们从关系型数据库中读取了三张表,得到了三个DataFrame
对象,但实际工作可能需要我们把他们的数据整合到一起。例如:emp_df
和emp2_df
其实都是员工的数据,而且数据结构完全一致,我们可以使用pandas
提供的concat
函数实现两个或多个DataFrame
的数据拼接,代码如下所示。
all_emp_df = pd.concat([emp_df, emp2_df])
输出:
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 NaN 10
3588 朱九真 会计 5566.0 2500 NaN 10
4466 苗人凤 销售员 3344.0 2500 NaN 30
5234 郭靖 出纳 5566.0 2000 NaN 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
9800 骆昊 架构师 7800.0 30000 5000.0 20
9900 王小刀 程序员 9800.0 10000 1200.0 20
9700 王大锤 程序员 9800.0 8000 600.0 20
上面的代码将两个代表员工数据的DataFrame
拼接到了一起,接下来我们使用merge
函数将员工表和部门表的数据合并到一张表中,代码如下所示。
先使用reset_index
方法重新设置all_emp_df
的索引,这样eno
不再是索引而是一个普通列,reset_index
方法的inplace
参数设置为True
表示,重置索引的操作直接在all_emp_df
上执行,而不是返回修改后的新对象。
all_emp_df.reset_index(inplace=True)
通过merge
函数合并数据,当然,也可以调用DataFrame
对象的merge
方法来达到同样的效果。
pd.merge(dept_df, all_emp_df, how='inner', on='dno')
输出:
dno dname dloc eno ename job mgr sal comm
0 10 会计部 北京 3577 杨过 会计 5566.0 2200 NaN
1 10 会计部 北京 3588 朱九真 会计 5566.0 2500 NaN
2 10 会计部 北京 5234 郭靖 出纳 5566.0 2000 NaN
3 10 会计部 北京 5566 宋远桥 会计师 7800.0 4000 1000.0
4 20 研发部 成都 2056 乔峰 架构师 7800.0 5000 1500.0
5 20 研发部 成都 3088 李莫愁 设计师 2056.0 3500 800.0
6 20 研发部 成都 3211 张无忌 程序员 2056.0 3200 NaN
7 20 研发部 成都 3233 丘处机 程序员 2056.0 3400 NaN
8 20 研发部 成都 3244 欧阳锋 程序员 3088.0 3200 NaN
9 20 研发部 成都 3251 张翠山 程序员 2056.0 4000 NaN
10 20 研发部 成都 7800 张三丰 总裁 NaN 9000 1200.0
11 20 研发部 成都 9800 骆昊 架构师 7800.0 30000 5000.0
12 20 研发部 成都 9900 王小刀 程序员 9800.0 10000 1200.0
13 20 研发部 成都 9700 王大锤 程序员 9800.0 8000 600.0
14 30 销售部 重庆 1359 胡一刀 销售员 3344.0 1800 200.0
15 30 销售部 重庆 3344 黄蓉 销售主管 7800.0 3000 800.0
16 30 销售部 重庆 4466 苗人凤 销售员 3344.0 2500 NaN
merge
函数的一个参数代表合并的左表、第二个参数代表合并的右表,有SQL编程经验的同学对这两个词是不是感觉到非常亲切。正如大家猜想的那样,DataFrame
对象的合并跟数据库中的表连接非常类似,所以上面代码中的how
代表了合并两张表的方式,有left
、right
、inner
、outer
四个选项;而on
则代表了基于哪个列实现表的合并,相当于 SQL 表连接中的连表条件,如果左右两表对应的列列名不同,可以用left_on
和right_on
参数取代on
参数分别进行指定。
如果对上面的代码稍作修改,将how
参数修改为left
,大家可以思考一下代码执行的结果。
pd.merge(dept_df, all_emp_df, how='left', on='dno')
运行结果比之前的输出多出了如下所示的一行,这是因为left
代表左外连接,也就意味着左表dept_df
中的数据会被完整的查出来,但是在all_emp_df
中又没有编号为40
部门的员工,所以对应的位置都被填入了空值。
17 40 运维部 天津 NaN NaN NaN NaN NaN NaN