如果使用 pandas 做数据分析,那么DataFrame
一定是被使用得最多的类型,它可以用来保存和处理异质的二维数据。这里所谓的“异质”是指DataFrame
中每个列的数据类型不需要相同,这也是它区别于 NumPy 二维数组的地方。DataFrame
提供了极为丰富的属性和方法,帮助我们实现对数据的重塑、清洗、预处理、透视、呈现等一系列操作。
创建DataFrame对象
通过二维数组创建DataFrame对象
代码:
1 | scores = np.random.randint(60, 101, (5, 3)) |
输出:
1 | 语文 数学 英语 |
通过字典创建DataFrame对象
代码:
1 | scores = { |
输出:
1 | 语文 数学 英语 |
读取CSV文件创建DataFrame对象
可以通过pandas
模块的read_csv
函数来读取 CSV 文件,read_csv
函数的参数非常多,下面介绍几个比较重要的参数。
sep
/delimiter
:分隔符,默认是,
。header
:表头(列索引)的位置,默认值是infer
,用第一行的内容作为表头(列索引)。index_col
:用作行索引(标签)的列。usecols
:需要加载的列,可以使用序号或者列名。true_values
/false_values
:哪些值被视为布尔值True
/False
。skiprows
:通过行号、索引或函数指定需要跳过的行。skipfooter
:要跳过的末尾行数。nrows
:需要读取的行数。na_values
:哪些值被视为空值。iterator
:设置为True
,函数返回迭代器对象。chunksize
:配合上面的参数,设置每次迭代获取的数据体量。
代码:
1 | df3 = pd.read_csv('data/2018年北京积分落户数据.csv', index_col='id') |
提示:上面代码中的CSV文件是用相对路径进行获取的,也就是说当前工作路径下有名为
data
的文件夹,而“2018年北京积分落户数据.csv”就在这个文件夹下。如果使用Windows系统,在写路径分隔符时也建议使用/
而不是\
,如果想使用\
,建议在字符串前面添加一个r
,使用原始字符串来避开转义字符,例如r'c:\new\data\2018年北京积分落户数据.csv'
。
输出:
1 | name birthday company score |
说明: 上面输出的内容隐去了姓名(name)和公司名称(company)字段中的部分信息。如果需要上面例子中的 CSV 文件,可以通过百度云盘获取,链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取码:e7b4。
读取Excel工作表创建DataFrame对象
可以通过pandas
模块的read_excel
函数来读取 Excel 文件,该函数与上面的read_csv
非常类似,多了一个sheet_name
参数来指定数据表的名称,但是不同于 CSV 文件,没有sep
或delimiter
这样的参数。假设有名为“2022年股票数据.xlsx”的 Excel 文件,里面有用股票代码命名的五个表单,分别是阿里巴巴(BABA)、百度(BIDU)、京东(JD)、亚马逊(AMZN)、甲骨文(ORCL)这五个公司2022年的股票数据,如果想加载亚马逊的股票数据,代码如下所示。
代码:
1 | df4 = pd.read_excel('data/2022年股票数据.xlsx', sheet_name='AMZN', index_col='Date') |
说明:上面例子中的 CSV 文件可以通过百度云盘获取,链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取码:e7b4。
输出:
1 | Open High Low Close Volume |
读取关系数据库二维表创建DataFrame对象
pandas
模块的read_sql
函数可以通过 SQL 语句从数据库中读取数据创建DataFrame
对象,该函数的第二个参数代表了需要连接的数据库。对于 MySQL 数据库,我们可以通过pymysql
或mysqlclient
来创建数据库连接(需要提前安装好三方库),得到一个Connection
对象,而这个对象就是read_sql
函数需要的第二个参数,代码如下所示。
代码:
1 | import pymysql |
提示:执行上面的代码需要先安装
pymysql
库,如果尚未安装,可以先在单元格中先执行魔法指令%pip install pymysql
,然后再运行上面的代码。上面的代码连接的是我部署在腾讯云上的 MySQL 数据库,公网 IP 地址:101.42.16.8
,用户名:guest
,密码:Guest.618
,数据库:hrs
,字符集:utf8mb4
,大家可以使用这个数据库,但是不要进行恶意的访问。hrs
数据库一共有三张表,分别是:tb_dept
(部门表)、tb_emp
(员工表)、tb_emp2
(员工表2)。
输出:
1 | ename job mgr sal comm dno |
执行上面的代码会出现一个警告,因为 pandas 库希望我们使用SQLAlchemy
三方库接入数据库,具体内容是:“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
,在 Jupyter 中可以使用%pip
魔法指令。
1 | %pip install sqlalchemy |
通过SQLAlchemy
的create_engine
函数创建Engine
对象作为read_sql
函数的第二个参数,此时read_sql
函数的第一个参数可以是 SQL 语句,也可以是二维表的表名。
1 | from sqlalchemy import create_engine |
说明:如果通过表名加载二维表数据,也可以将上面的函数换成
read_sql_table
。
我们再来加载部门表的数据创建DataFrame
对象。
1 | df6 = pd.read_sql('select dno, dname, dloc from tb_dept', engine, index_col='dno') |
说明:如果通过 SQL 查询获取数据,也可以将上面的函数换成
read_sql_query
。
输出:
1 | dname dloc |
在完成数据加载后,如果希望释放数据库连接,可以使用下面的代码。
1 | engine.connect().close() |
基本属性和方法
在开始讲解DataFrame
的属性和方法前,我们先从之前提到的hrs
数据库中读取三张表的数据,创建出三个DataFrame
对象,完整的代码如下所示。
1 | from sqlalchemy import create_engine |
得到的三个DataFrame
对象如下所示。
部门表(dept_df
),其中dno
是部门的编号,dname
和dloc
分别是部门的名称和所在地。
1 | dname dloc |
员工表(emp_df
),其中eno
是员工编号,ename
、job
、mgr
、sal
、comm
和dno
分别代表员工的姓名、职位、主管编号、月薪、补贴和部门编号。
1 | ename job mgr sal comm dno |
说明:在数据库中
mgr
和comm
两个列的数据类型是int
,但是因为有缺失值(空值),读取到DataFrame
之后,列的数据类型变成了float
,因为我们通常会用float
类型的NaN
来表示空值。
员工表(emp2_df
),跟上面的员工表结构相同,但是保存了不同的员工数据。
1 | ename job mgr sal comm dno |
DataFrame
对象的属性如下表所示。
属性名 | 说明 |
---|---|
at / iat |
通过标签获取DataFrame 中的单个值。 |
columns |
DataFrame 对象列的索引 |
dtypes |
DataFrame 对象每一列的数据类型 |
empty |
DataFrame 对象是否为空 |
loc / iloc |
通过标签获取DataFrame 中的一组值。 |
ndim |
DataFrame 对象的维度 |
shape |
DataFrame 对象的形状(行数和列数) |
size |
DataFrame 对象中元素的个数 |
values |
DataFrame 对象的数据对应的二维数组 |
关于DataFrame
的方法,首先需要了解的是info()
方法,它可以帮助我们了解DataFrame
的相关信息,如下所示。
代码:
1 | emp_df.info() |
输出:
1 | <class 'pandas.core.frame.DataFrame'> |
如果需要查看DataFrame
的头部或尾部的数据,可以使用head()
或tail()
方法,这两个方法的默认参数是5
,表示获取DataFrame
最前面5行或最后面5行的数据,如下所示。
1 | emp_df.head() |
输出:
1 | ename job mgr sal comm dno |
操作数据
索引和切片
如果要获取DataFrame
的某一列,例如取出上面emp_df
的ename
列,可以使用下面的两种方式。
1 | emp_df.ename |
或者
1 | emp_df['ename'] |
执行上面的代码可以发现,我们获得的是一个Series
对象。事实上,DataFrame
对象就是将多个Series
对象组合到一起的结果。
如果要获取DataFrame
的某一行,可以使用整数索引或我们设置的索引,例如取出员工编号为2056
的员工数据,代码如下所示。
1 | emp_df.iloc[1] |
或者
1 | emp_df.loc[2056] |
通过执行上面的代码我们发现,单独取DataFrame
的某一行或某一列得到的都是Series
对象。我们当然也可以通过花式索引来获取多个行或多个列的数据,花式索引的结果仍然是一个DataFrame
对象。
获取多个列:
1 | emp_df[['ename', 'job']] |
获取多个行:
1 | emp_df.loc[[2056, 7800, 3344]] |
如果要获取或修改DataFrame
对象某个单元格的数据,需要同时指定行和列的索引,例如要获取员工编号为2056
的员工的职位信息,代码如下所示。
1 | emp_df['job'][2056] |
或者
1 | emp_df.loc[2056]['job'] |
或者
1 | emp_df.loc[2056, 'job'] |
我们推荐大家使用第三种做法,因为它只做了一次索引运算。如果要将该员工的职位修改为“架构师”,可以使用下面的代码。
1 | emp_df.loc[2056, 'job'] = '架构师' |
当然,我们也可以通过切片操作来获取多行多列,相信大家一定已经想到了这一点。
1 | emp_df.loc[2056:3344] |
输出:
1 | ename job mgr sal comm dno |
数据筛选
上面我们提到了花式索引,相信大家已经联想到了布尔索引。跟ndarray
和Series
一样,我们可以通过布尔索引对DataFrame
对象进行数据筛选,例如我们要从emp_df
中筛选出月薪超过3500
的员工,代码如下所示。
1 | emp_df[emp_df.sal > 3500] |
输出:
1 | ename job mgr sal comm dno |
当然,我们也可以组合多个条件来进行数据筛选,例如从emp_df
中筛选出月薪超过3500
且部门编号为20
的员工,代码如下所示。
1 | emp_df[(emp_df.sal > 3500) & (emp_df.dno == 20)] |
输出:
1 | ename job mgr sal comm dno |
除了使用布尔索引,DataFrame
对象的query
方法也可以实现数据筛选,query
方法的参数是一个字符串,它代表了筛选数据使用的表达式,而且更符合 Python 程序员的使用习惯。下面我们使用query
方法将上面的效果重新实现一遍,代码如下所示。
1 | emp_df.query('sal > 3500 and dno == 20') |