欢迎来到Pandas数据分析基础课程。
在今天的商业世界,数据是最宝贵的资产之一。无论是分析销售趋势、评估营销活动,还是进行财务预测,我们都需要强大的工具来处理和理解数据。
本章将带领大家掌握Python中最核心的数据分析库——Pandas
。
Python语言凭借其简洁的语法和强大的库生态,已成为商业数据分析的首选。
而在Python的众多库中,Pandas
无疑是皇冠上的明珠。
对于商学院的同学来说,大家最熟悉的数据格式莫过于Excel电子表格。
Pandas中的DataFrame
就可以被看作是Python世界里的’超级Excel’。
它是一个二维表格结构,拥有带标签的轴(行和列),是我们进行一切复杂分析的基石。
我们将循序渐进,全面掌握Pandas的核心操作。
Pandas库提供了多种方式来构建一个DataFrame,非常灵活。
我们将介绍几种最常见、最实用的方法。
这是最直观、最常用的创建小规模DataFrame的方法。
核心思想: 将列名(键)与该列的数据(值,通常是一个列表)对应起来。
在Python中,字典使用花括号{}
定义,由’键: 值’对组成。
对于创建DataFrame而言:
我们想创建一个包含学生姓名、年龄和身高信息的数据表。
首先,构建一个符合要求的字典:
注意:每个列表的长度必须完全相同。
pd.DataFrame()
构造函数创建DataFrame的核心函数是 pd.DataFrame()
。
我们将刚才创建的字典作为参数传递给它。
Pandas会自动将字典的键作为列标签,将值作为列的内容。
Numpy是Python中用于科学计算的核心库,核心是N维数组对象。
如果你的数据已经存储在一个Numpy数组中,也可以非常方便地将其转换为DataFrame。
首先,我们使用numpy
库创建一个二维数组。
这个数组只有纯粹的数值数据,没有任何标签信息。
与从字典创建不同,Numpy数组本身不包含列名信息。
因此,在调用pd.DataFrame()
时,我们需要通过columns
参数手动指定列名。
行索引同样是默认生成的。
在真实的商业分析场景中,数据绝大多数时候都存储在外部文件中。
Pandas提供了强大的函数来直接从这些源读取数据。这是最常用的方式。
CSV (Comma-Separated Values) 是一种极其通用的纯文本格式。
使用pd.read_csv()
函数。
这是商科学生最常用的功能。
使用pd.read_excel()
函数。
对于存储在数据库中的数据,Pandas也可以通过连接直接查询并加载。
使用pd.read_sql()
函数。
我们将在后续章节详细探讨文件读写。
创建或加载DataFrame后,第一步通常是快速浏览其内容和结构,以获得对数据的初步认识。
Pandas提供了几个非常有用的方法。
head()
与tail()
对于大型数据集,我们不可能一次性查看所有数据。
df.head()
: 查看前几行数据(默认为5行)。df.tail()
: 查看后几行数据(默认为5行)。这是快速预览大规模数据集最有效的方法。
head()
方法示例我们对之前创建的学生信息表使用head()
。
由于总共只有4行,head()
显示了所有数据。
info()
df.info()
方法非常强大,它提供了DataFrame的紧凑摘要。
这是获取数据’体检报告’的首选命令。
info()
方法示例<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 name 4 non-null object
1 age 4 non-null int64
2 height 4 non-null float64
dtypes: float64(1), int64(1), object(1)
memory usage: 224.0+ bytes
info()
输出详解info()
的输出告诉我们:
pandas.core.frame.DataFrame
。RangeIndex: 4 entries, 0 to 3
。Data columns (total 3 columns)
。Non-Null Count
: 非缺失值的数量。Dtype
: 该列的数据类型(object
通常指字符串,int64
是整数,float64
是小数)。memory usage
。Pandas提供了非常灵活的方式来选择数据的子集。
我们先快速了解两种核心方法,后续章节会深入讲解。
基于某一列的条件进行筛选,返回满足条件的行。
例如,筛选出年龄大于30岁的学生:
.loc
与.iloc
这是进行切片操作的两种主要方法。
.loc
: 基于标签(行和列的名称)进行选择。.iloc
: 基于整数位置(从0开始的索引)进行选择。这个概念非常重要,我们稍后会用一整个章节来详细拆解。
loc
示例:按标签切片选择行标签为1到3,列标签为’name’和’height’的数据。
iloc
示例:按位置切片选择位置为1到2的行(不含3),位置为0到1的列(不含2)。
现在,让我们将理论付诸实践。
任务: 创建一个简单的企业员工信息数据集,并进行初步分析。
部门名称
, 员工数量
, 平均薪资
三列。员工数量
大于等于30的部门。平均薪资
的中位数。首先,我们将数据存储在Python列表中。
使用我们学过的从字典创建DataFrame的方法。
调用head()
和info()
方法。
使用布尔索引筛选出员工数量 >= 30
的部门。
选取平均薪资
这一列,并使用Numpy的median
函数计算中位数。
import pandas as pd
import numpy as np
department = ['研发部', '财务部', '市场部', '销售部', '人力资源部']
employee_num = [30, 20, 25, 35, 15]
salary_mean = [8000, 10000, 9000, 12000, 8500]
df = pd.DataFrame({'部门名称': department, '员工数量': employee_num, '平均薪资': salary_mean})
print("--- 创建的DataFrame ---")
print(df)
print("\n--- 基本信息 ---")
df.info()
print("\n--- 员工数量>=30的部门 ---")
print(df[df['员工数量'] >= 30])
print("\n--- 平均薪资中位数 ---")
print(np.median(df['平均薪资']))
在真实的商业环境中,数据很少是通过手动在代码中创建的。
绝大多数情况下,数据存储在各种格式的文件中,如Excel、CSV等。
Pandas 提供了非常强大且易于使用的 I/O (Input/Output) 工具。
pd.read_excel()
pd.read_excel()
是我们最常使用的函数之一。
它可以直接读取 .xls
或 .xlsx
格式的文件,甚至可以从一个URL读取。
read_excel
示例:从URL读取read_excel
的重要参数:sheet_name
一个Excel文件可能包含多个工作表(sheet)。
通过sheet_name
参数,可以指定读取哪个工作表。
sheet_name=0
: 读取第一个工作表。sheet_name='Sheet1'
: 读取名为’Sheet1’的工作表。sheet_name=None
: 读取所有工作表,返回一个字典。read_excel
的重要参数:index_col
默认情况下,Pandas会生成从0开始的整数索引。
index_col
参数可以指定文件中的某一列作为DataFrame的行索引。
pd.read_csv()
CSV (逗号分隔值) 文件是另一种非常普遍的数据存储格式。
pd.read_csv()
是用来读取CSV文件的函数。
由于其纯文本特性,CSV具有极好的跨平台兼容性。
read_csv
的重要参数:delimiter
虽然CSV通常用逗号分隔,但有时也可能用分号、制表符等。
delimiter
(或 sep
) 参数可以指定文件中的分隔符。
read_csv
的重要参数:encoding
处理包含中文的CSV文件时,encoding
参数至关重要,以避免乱码。
encoding='utf-8'
: 最常用的国际编码。encoding='gbk'
: 简体中文环境下常见的编码。to_*
方法分析和处理完数据后,我们通常需要将结果保存到文件中。
Pandas的DataFrame对象拥有一系列 to_*
方法,可以方便地将数据写入不同格式的文件。
to_excel()
首先,我们创建一个简单的DataFrame作为示例。
to_excel()
基本用法将这个DataFrame写入名为 演示.xlsx
的文件中。
执行后,你会在当前目录下发现一个新生成的演示.xlsx
文件。
to_excel()
的重要参数:index
index=False
可以阻止Pandas将DataFrame的索引写入文件。
这是to_excel()
中最常用的参数之一。
to_excel()
的其他参数columns
: 一个列表,用于指定要写入文件的列。sheet_name
: 字符串,指定写入的工作表的名称。例如,只将 A列
的数据写入Excel,不包含行索引,并命名工作表为’MyData’:
to_csv()
与写入Excel类似,我们可以使用 to_csv()
方法将DataFrame保存为CSV文件。
to_csv()
的encoding
参数:解决中文乱码的关键当数据中包含中文字符时,直接用Excel打开CSV文件可能会出现乱码。
为了解决这个问题,建议使用 'utf_8_sig'
编码。它会在文件开头加入一个特殊标记(BOM),帮助Excel正确识别UTF-8编码。
任务: 读取一个CSV格式的财务报表,并将其转换为Excel格式,以便于不熟悉编程的同事进行查阅。
eastmoney.csv
文件。eastmoney.xlsx
的Excel文件中。掌握了文件读写后,我们来学习一项至关重要的数据分析技能:数据筛选。
财务数据集通常非常庞大,我们往往只对其中一部分数据感兴趣。
“筛选”就是从庞大的数据集中精确地提取出我们所需要的信息子集的过程。
为了方便演示,我们首先创建一个3x3的DataFrame实例,并为其指定清晰的行和列标签。
我们所有的筛选操作都将基于这个数据表进行。
选取DataFrame中的一列或多列是最基本的操作。
可以通过 df['列名']
的方式实现。返回的结果是一个Pandas Series
对象。
注意输出结果是一维的,保留了行索引,但没有二维表格结构。
如果你希望选取单列后仍然保持DataFrame的二维格式,可以在列名外再加一层方括号 df[['列名']]
。
将一个包含多个列名的列表传递给方括号即可。
选取行的方式比选取列稍微复杂一些,Pandas为此提供了专门的索引器(indexer)。
.iloc
vs .loc
这是Pandas筛选操作中最核心、也最容易混淆的概念。
.iloc
(Integer Location): 基于整数位置的索引。.loc
(Location): 基于标签名称的索引。一个简单的记忆方法:i
in iloc
stands for “integer”。
.iloc
按整数位置选取.iloc
总是把DataFrame看作一个从0开始计数的数组。
.iloc
的切片规则和Python列表一样,是“左闭右开”的。
.iloc
选取单行要选取单行,必须使用.iloc
或.loc
。
例如,选取最后一行(位置为-1):
如果直接使用 data[-1]
,程序会报错,因为它会尝试寻找一个名为-1
的列。
.loc
按标签名称选取.loc
使用行索引的具体名称来选取。
注意.loc
的切片是“两端都包含”的,例如data_filter_demo.loc['r1':'r3']
会包含r1
, r2
, r3
三行。
.loc
vs .iloc
方法 | 索引依据 | 切片行为 | 示例 |
---|---|---|---|
.loc |
标签名称 | 两端闭合 | df.loc['row1':'row3', 'col_A'] |
.iloc |
整数位置 | 左闭右开 | df.iloc[0:3, 0] |
这是Pandas的必考点,请务必掌握。
当我们需要同时根据行和列的条件来选取数据时,.loc
和.iloc
的威力就真正体现出来了。
它们的语法是:df.indexer[行条件, 列条件]
.loc
直接选取区块按标签名选取r1
,r2
行和c1
,c3
列。
.iloc
直接选取区块按整数位置选取前2行(位置0,1)和第1、3列(位置0,2)。
两种方法得到了相同的结果,但其内部逻辑完全不同。
要精确地选取某行某列的单个值,推荐的写法是先用 .iloc
或 .loc
定位到行,再用 []
定位到列。
更规范的写法是 data_filter_demo.at['r1', 'c3']
或 data_filter_demo.iat[0, 2]
,但初学阶段掌握前一种即可。
这是数据筛选中最强大的功能:根据数据本身的值来提取子集。
这通常被称为布尔索引 (Boolean Indexing)。
我们首先创建一个布尔条件,例如 data_filter_demo['c1'] > 1
。
Pandas会逐行检查这个条件,返回一个由True
和False
组成的Series。
这个True
/False
序列就是我们的“筛选器”或“掩码 (mask)”。
然后,我们将这个布尔Series传给DataFrame的 []
中。
Pandas就会只返回那些在筛选器中对应值为True
的行。
当有多个筛选条件时,可以使用逻辑运算符 &
(与) 和 |
(或)。
关键规则: 每个独立的条件都必须用小括号 ()
括起来。
筛选 c1
列中数字大于1 且 (&
) c2
列中数字小于8的行。
只有 r2
行同时满足这两个条件,因此被筛选出来。
任务: 对 eastmoney.xlsx
文件中的数据进行多项筛选操作。
eastmoney.xlsx
文件。.iloc
提取第2行到第3行的数据。在复杂的商业分析中,数据往往分散在多个不同的表格或文件中。
要进行综合分析,就必须先把这些表连接起来。
Pandas提供了三大核心函数:merge()
, concat()
和 append()
。
我们创建两个简单的DataFrame,df1
包含公司评分,df2
包含公司股价。
merge()
函数merge()
函数类似于数据库操作中的 JOIN
,它根据一个或多个共同的键 (key)将不同DataFrame的行连接起来。
这是进行关系型数据合并的首选方法。
merge
类型1:内连接 (Inner Join)merge()
默认执行内连接,只保留两个表中共同拥有的键。
’百度’和’京东’因为不是共有的键,所以被丢弃了。
merge
类型2:外连接 (Outer Join)通过设置 how='outer'
,我们可以保留所有数据,即使它只出现在一个表中。
对于缺失的数据(如’百度’的股价),Pandas会自动填充为 NaN
(Not a Number)。
merge
类型3:左连接 (Left Join)通过设置 how='left'
,保留左边表(第一个参数df1
)的所有行。
df1
中所有的公司都被保留,而df2
中独有的’京东’被舍弃。右连接 (how='right'
) 则反之。
concat()
函数concat()
函数用于将多个DataFrame沿着一个轴(行或列)进行拼接,类似于将几张表格头尾相连地“粘”在一起。
concat
按行拼接 (默认)这是最常见的用法,即把一个DataFrame堆叠在另一个的下方 (axis=0
)。
不同表的列被合并,缺失的位置用NaN
填充。注意,原始的行索引被保留了。
concat
处理索引:ignore_index=True
如果想生成一套新的连续索引,可以添加参数 ignore_index=True
。
concat
按列拼接通过设置 axis=1
,concat()
也可以将DataFrame左右拼接起来。
效果是根据行索引将两个表并排放在一起。
append()
函数 (即将弃用)append()
方法可以看作是 concat(axis=0)
的一个简化版。
函数即将弃用 (Deprecation Warning)
Pandas的 .append()
方法已被标记为不推荐使用 (deprecated)。官方推荐使用 pd.concat()
来替代。在我们的新项目中,应当养成使用 pd.concat()
的好习惯。
任务: 将贵州茅台2019年和2020年的利润表数据(两个Excel文件)合并成一个单一的数据集。
贵州茅台_利润表_2019年.xlsx
和 2020年.xlsx
。concat
函数按行方向将两个DataFrame拼接起来,并忽略原索引。append
函数实现相同的效果 (作为对比)。# 准备环境
import pandas as pd
dummy_df_2019 = pd.DataFrame({'Unnamed: 0': [0], 'ts_code': ['600519.SH'], 'ann_date': [20200320], 'end_date': [20191231], 'total_revenue': [8.8e10]})
dummy_df_2020 = pd.DataFrame({'Unnamed: 0': [0], 'ts_code': ['600519.SH'], 'ann_date': [20210330], 'end_date': [20201231], 'total_revenue': [9.7e10]})
dummy_df_2019.to_excel("贵州茅台_利润表_2019年.xlsx", index=False)
dummy_df_2020.to_excel("贵州茅台_利润表_2020年.xlsx", index=False)
# --- 正式代码 ---
data_2019 = pd.read_excel("贵州茅台_利润表_2019年.xlsx").drop(columns=["Unnamed: 0"])
data_2020 = pd.read_excel("贵州茅台_利润表_2020年.xlsx").drop(columns=["Unnamed: 0"])
data_concat = pd.concat([data_2019,data_2020],axis=0,ignore_index=True)
# data_append = data_2019.append(data_2020,ignore_index=True) # 旧方法
# 为了与原文保持一致,我们使用旧方法,但在注释中说明
data_append = pd.concat([data_2019, data_2020], ignore_index=True)
print(data_append)
现在,是时候将这些独立的技能串联起来,解决更接近真实世界的商业分析问题了。
本节将通过两个递进的实战案例,带领大家完成一个完整的小型数据处理流程。
任务: 从Tushare获取贵州茅台最新的利润表数据,提取2023年年报中的关键财务指标,并进行数据去重。
income
接口获取贵州茅台的所有利润表数据。end_date
为 “20231231” 的数据。'ts_code'
, 'end_date'
, 'total_revenue'
, 'total_cogs'
, 'n_income'
, 'ebit'
这几个关键列。end_date
列去除重复的报告期数据。获取数据后,使用布尔索引筛选出2023年年报。
通过传入列名列表,选取我们关心的关键指标。
使用drop_duplicates()
方法,确保每个报告期只有一条最新的记录。
# 为保证可复现,此处使用模拟数据
import pandas as pd
data = {
'ts_code': ['600519.SH']*5,
'ann_date': [20240330, 20231020, 20230802, 20230428, 20230330],
'end_date': ['20231231', '20230930', '20230630', '20230331', '20221231'],
'total_revenue': [1.5e11, 1.1e11, 7.5e10, 3.9e10, 1.3e11],
'total_cogs': [1.2e10, 9.0e9, 6.0e9, 3.0e9, 1.1e10],
'n_income': [7.4e10, 5.5e10, 3.6e10, 2.0e10, 6.2e10],
'ebit': [9.9e10, 7.4e10, 4.8e10, 2.6e10, 8.3e10]
}
df = pd.DataFrame(data)
# --- 正式代码 ---
df_1 = df[df["end_date"]=="20231231"]
df_2 = df[['ts_code','end_date','total_revenue','total_cogs','n_income','ebit']]
df_3 = df.drop_duplicates("end_date")
print("筛选2023年年报结果:")
print(df_1)
print("\n选取关键财务指标结果:")
print(df_2.head())
print("\n按报告期去重后结果:")
print(df_3.head())
任务: 批量获取贵州茅台、五粮液、泸州老窖过去五年的三大财务报表,进行数据处理后结构化存储。
这是一个更复杂的、接近真实工作流的案例。
.rename()
函数可以接收一个字典,将旧的列名映射到新的列名。
语法: df.rename(columns={'旧名1': '新名1', '旧名2': '新名2'})
我们将编写一个辅助函数来完成这个任务。
pd.ExcelWriter
是向同一个Excel文件写入多个Sheet的标准方法。
流程:
writer
对象: writer = pd.ExcelWriter('文件名.xlsx')
df1.to_excel(writer, sheet_name='表1')
, df2.to_excel(writer, sheet_name='表2')
writer.save()
(新版pandas中writer.close()
也可以)这个函数读取一个预定义的Excel文件(包含中英文名对应关系),创建一个’英-中’映射字典,然后用.rename()
方法重命名DataFrame的列。
append
或concat
到该公司的总表DataFrame上。rename_col
汉化总表。ExcelWriter
将三张汉化后的总表写入一个以公司名命名的Excel文件中。定义好公司、代码和年份列表后,直接调用主函数即可。
最后,读取生成的一个文件(如“贵州茅台.xlsx”)中的某个Sheet,打印其内容,以验证整个流程是否正确执行。
import pandas as pd
# --- 准备模拟环境 ---
# 模拟Tushare API
class MockTushare:
def pro_api(self, token): return self
def balancesheet(self, ts_code, period): return pd.DataFrame({'ts_code': [ts_code], 'end_date': [period], 'total_assets': [5e11]})
def income(self, ts_code, period): return pd.DataFrame({'ts_code': [ts_code], 'end_date': [period], 'total_revenue': [1.5e11]})
def cashflow(self, ts_code, period): return pd.DataFrame({'ts_code': [ts_code], 'end_date': [period], 'n_cashflow_act': [6e10]})
ts = MockTushare()
# 模拟"重命名.xlsx"
rename_df = pd.DataFrame({'名称': ['ts_code', 'end_date', 'total_assets', 'total_revenue', 'n_cashflow_act'], '描述': ['股票代码', '报告期', '总资产', '营业总收入', '经营活动现金流净额']})
rename_df.to_excel("重命名.xlsx", index=False)
# --- 正式代码 ---
pro = ts.pro_api('mock_token')
def rename_col(data):
rename_sheet = pd.read_excel("重命名.xlsx")
eng = rename_sheet["名称"].tolist()
chi = rename_sheet["描述"].tolist()
re_dict = dict(zip(eng,chi))
data = data.rename(columns=re_dict)
return data
def access_data(comps,codes,years):
for i in range(len(comps)):
df_balance = pd.DataFrame()
df_income = pd.DataFrame()
df_cash = pd.DataFrame()
for year in years:
df_balance0 = pro.balancesheet(ts_code=codes[i],period=str(year)+'1231')
df_income0 = pro.income(ts_code=codes[i],period=str(year)+'1231')
df_cash0 = pro.cashflow(ts_code=codes[i],period=str(year)+'1231')
df_balance = pd.concat([df_balance, df_balance0], ignore_index=True)
df_income = pd.concat([df_income, df_income0], ignore_index=True)
df_cash = pd.concat([df_cash, df_cash0], ignore_index=True)
df_balance = rename_col(df_balance)
df_income = rename_col(df_income)
df_cash = rename_col(df_cash)
with pd.ExcelWriter(comps[i]+'.xlsx') as writer:
df_balance.to_excel(writer, '资产负债表', index=False)
df_income.to_excel(writer, '利润表', index=False)
df_cash.to_excel(writer, '现金流量表', index=False)
comps = ['贵州茅台','五粮液','泸州老窖']
codes = ['600519.SH','000858.SZ','000568.SZ']
years = [2023,2022,2021,2020,2019]
access_data(comps,codes,years)
data = pd.read_excel("贵州茅台.xlsx", sheet_name="资产负债表")
print(data)
今天,我们系统学习了Pandas数据处理的核心技能:
.loc
, .iloc
和布尔索引精确定位数据。merge
和concat
整合多源数据。这些是进行任何复杂数据分析的基石。
掌握了基础操作后,你将能够探索Pandas更高级的功能:
groupby
聚合操作。Matplotlib
和Seaborn
进行数据可视化。数据分析的旅程才刚刚开始,祝大家学有所成!
提问环节