04 Pandas数据分析基础

课程目标:开启商业数据分析之旅

欢迎来到Pandas数据分析基础课程。

在今天的商业世界,数据是最宝贵的资产之一。无论是分析销售趋势、评估营销活动,还是进行财务预测,我们都需要强大的工具来处理和理解数据。

本章将带领大家掌握Python中最核心的数据分析库——Pandas

核心工具:为什么选择Python和Pandas?

Python语言凭借其简洁的语法和强大的库生态,已成为商业数据分析的首选。

而在Python的众多库中,Pandas无疑是皇冠上的明珠。

  • 高性能: 底层由C语言编写,处理速度快。
  • 易于使用: 语法直观,学习曲线平缓。
  • 功能强大: 提供丰富的数据结构和分析工具。

核心概念:DataFrame是Python界的’超级Excel’

对于商学院的同学来说,大家最熟悉的数据格式莫过于Excel电子表格。

Pandas中的DataFrame就可以被看作是Python世界里的’超级Excel’。

DataFrame Structure DATA Column A Column B Column C 0 1 2 Columns Index

它是一个二维表格结构,拥有带标签的轴(行和列),是我们进行一切复杂分析的基石。

本章学习路线图

我们将循序渐进,全面掌握Pandas的核心操作。

  1. 创建与初识: 从零开始构建你的第一个DataFrame。
  2. 文件读写: 学习如何与Excel、CSV等外部文件交互。
  3. 数据筛选: 精准地从海量数据中提取所需信息。
  4. 拼接合并: 将来自不同来源的数据整合在一起。
  5. 综合实战: 运用所学知识解决真实的商业分析问题。

DataFrame的多种创建方式

Pandas库提供了多种方式来构建一个DataFrame,非常灵活。

我们将介绍几种最常见、最实用的方法。

创建方式一:从Python字典创建

这是最直观、最常用的创建小规模DataFrame的方法。

核心思想: 将列名(键)与该列的数据(值,通常是一个列表)对应起来。

字典的结构

在Python中,字典使用花括号{}定义,由’键: 值’对组成。

对于创建DataFrame而言:

  • 键 (Key): 必须是字符串,将成为DataFrame的列名
  • 值 (Value): 通常是列表,其元素将成为该列的数据

示例:构建学生信息字典

我们想创建一个包含学生姓名、年龄和身高信息的数据表。

首先,构建一个符合要求的字典:

dict_data = {'name': ['Alice', 'Bob', 'Charlie', 'David'],
             'age': [25, 30, 45, 21],
             'height': [165.4, 170.2, 178.6, 160.0]
            }

注意:每个列表的长度必须完全相同。

调用pd.DataFrame()构造函数

创建DataFrame的核心函数是 pd.DataFrame()

我们将刚才创建的字典作为参数传递给它。

import pandas as pd

# 假设 dict_data 已被定义
df = pd.DataFrame(dict_data)

Pandas会自动将字典的键作为列标签,将值作为列的内容。

完整代码与结果展示

Table 1: 通过Python字典创建的DataFrame
import pandas as pd

dict_data = {'name': ['Alice', 'Bob', 'Charlie', 'David'],
       'age': [25, 30, 45, 21],
       'height': [165.4, 170.2, 178.6, 160.0]
      }
df_dict = pd.DataFrame(dict_data)
df_dict

创建方式二:从Numpy数组创建

Numpy是Python中用于科学计算的核心库,核心是N维数组对象。

如果你的数据已经存储在一个Numpy数组中,也可以非常方便地将其转换为DataFrame。

示例:构建一个3x3的Numpy数组

首先,我们使用numpy库创建一个二维数组。

import numpy as np

ndarray_data = np.array([[1,2,3], [4,5,6], [7,8,9]])

这个数组只有纯粹的数值数据,没有任何标签信息。

转换时需手动指定列名

与从字典创建不同,Numpy数组本身不包含列名信息。

因此,在调用pd.DataFrame()时,我们需要通过columns参数手动指定列名。

import pandas as pd
# 假设 ndarray_data 已被定义

df = pd.DataFrame(ndarray_data, columns=['A', 'B', 'C'])

完整代码与结果展示

Table 2: 通过Numpy数组创建的DataFrame
import pandas as pd
import numpy as np

ndarray_data = np.array([[1,2,3], [4,5,6], [7,8,9]])
df_numpy = pd.DataFrame(ndarray_data, columns=['A', 'B', 'C'])
df_numpy

行索引同样是默认生成的。

创建方式三:从外部文件创建

在真实的商业分析场景中,数据绝大多数时候都存储在外部文件中。

Pandas提供了强大的函数来直接从这些源读取数据。这是最常用的方式。

从CSV文件读取

CSV (Comma-Separated Values) 是一种极其通用的纯文本格式。

使用pd.read_csv()函数。

import pandas as pd

# 假设当前目录下有一个名为 data.csv 的文件
df = pd.read_csv('data.csv')

从Excel文件读取

这是商科学生最常用的功能。

使用pd.read_excel()函数。

import pandas as pd

# 假设当前目录下有一个名为 data.xlsx 的文件
df = pd.read_excel('data.xlsx')

从SQL数据库读取

对于存储在数据库中的数据,Pandas也可以通过连接直接查询并加载。

使用pd.read_sql()函数。

import pandas as pd
import sqlite3

con = sqlite3.connect('my_database.db')
df = pd.read_sql('SELECT * FROM my_table', con)

我们将在后续章节详细探讨文件读写。

创建后第一步:快速浏览数据

创建或加载DataFrame后,第一步通常是快速浏览其内容和结构,以获得对数据的初步认识。

Pandas提供了几个非常有用的方法。

查看首尾数据:head()tail()

对于大型数据集,我们不可能一次性查看所有数据。

  • df.head(): 查看几行数据(默认为5行)。
  • df.tail(): 查看几行数据(默认为5行)。

这是快速预览大规模数据集最有效的方法。

head()方法示例

我们对之前创建的学生信息表使用head()

Table 3: 查看DataFrame的前5行
# 我们重新定义一下df_dict,因为它的行数小于5
df_dict.head()

由于总共只有4行,head()显示了所有数据。

获取摘要信息:info()

df.info()方法非常强大,它提供了DataFrame的紧凑摘要。

这是获取数据’体检报告’的首选命令。

info()方法示例

print(df_dict.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岁的学生:

Table 4: 使用布尔索引筛选年龄大于30的记录
df_dict[df_dict['age'] > 30]

筛选方法二:.loc.iloc

这是进行切片操作的两种主要方法。

  • .loc: 基于标签(行和列的名称)进行选择。
  • .iloc: 基于整数位置(从0开始的索引)进行选择。

这个概念非常重要,我们稍后会用一整个章节来详细拆解。

loc示例:按标签切片

选择行标签为1到3,列标签为’name’和’height’的数据。

Table 5: 使用.loc按标签切片
df_dict.loc[1:3, ['name', 'height']]

iloc示例:按位置切片

选择位置为1到2的行(不含3),位置为0到1的列(不含2)。

Table 6: 使用.iloc按位置切片
df_dict.iloc[1:3, 0:2]

实践案例:创建企业员工信息数据集

现在,让我们将理论付诸实践。

任务: 创建一个简单的企业员工信息数据集,并进行初步分析。

案例要求

  1. 创建一个DataFrame,包含部门名称, 员工数量, 平均薪资三列。
  2. 使用提供的数据填充DataFrame。
  3. 创建后,输出DataFrame的前5行数据。
  4. 输出该DataFrame的基本信息。
  5. 筛选出所有员工数量大于等于30的部门。
  6. 计算所有部门平均薪资的中位数。

步骤1:准备数据

首先,我们将数据存储在Python列表中。

department = ['研发部', '财务部', '市场部', '销售部', '人力资源部']
employee_num = [30, 20, 25, 35, 15]
salary_mean = [8000, 10000, 9000, 12000, 8500]

步骤2:创建DataFrame

使用我们学过的从字典创建DataFrame的方法。

import pandas as pd
import numpy as np

# (接上一步)
df_employee = pd.DataFrame({'部门名称': department, 
                            '员工数量': employee_num, 
                            '平均薪资': salary_mean})

步骤3 & 4:查看数据和信息

调用head()info()方法。

步骤5:筛选数据

使用布尔索引筛选出员工数量 >= 30的部门。

Table 7: 员工数量大于等于30的部门
df_employee[df_employee['员工数量'] >= 30]

步骤6:计算中位数

选取平均薪资这一列,并使用Numpy的median函数计算中位数。

print(f"平均薪资的中位数是: {np.median(df_employee['平均薪资'])}")

完整参考答案

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读取

Table 8: 从URL读取的Excel数据
import pandas as pd
# 从一个公开的URL读取Excel数据
data_excel_url = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20230430/xlsx/1652582851436634112.xlsx")
data_excel_url.head()

read_excel的重要参数:sheet_name

一个Excel文件可能包含多个工作表(sheet)。

通过sheet_name参数,可以指定读取哪个工作表。

  • sheet_name=0: 读取第一个工作表。
  • sheet_name='Sheet1': 读取名为’Sheet1’的工作表。
  • sheet_name=None: 读取所有工作表,返回一个字典。
# 假设本地有 data.xlsx 文件,且我们想读取第一个sheet
data = pd.read_excel('data.xlsx', sheet_name=0)

read_excel的重要参数:index_col

默认情况下,Pandas会生成从0开始的整数索引。

index_col参数可以指定文件中的某一列作为DataFrame的行索引。

# 假设 data.xlsx 的第一列是唯一的ID,我们想用它作为索引
data = pd.read_excel('data.xlsx', index_col=0)

读取文件:pd.read_csv()

CSV (逗号分隔值) 文件是另一种非常普遍的数据存储格式。

pd.read_csv() 是用来读取CSV文件的函数。

# 假设本地有 data.csv 文件
data = pd.read_csv('data.csv')

由于其纯文本特性,CSV具有极好的跨平台兼容性。

read_csv的重要参数:delimiter

虽然CSV通常用逗号分隔,但有时也可能用分号、制表符等。

delimiter (或 sep) 参数可以指定文件中的分隔符。

# 读取一个用分号分隔的文件
data = pd.read_csv('data.csv', delimiter=';')

read_csv的重要参数:encoding

处理包含中文的CSV文件时,encoding参数至关重要,以避免乱码。

  • encoding='utf-8': 最常用的国际编码。
  • encoding='gbk': 简体中文环境下常见的编码。
# 假设 data.csv 是一个包含中文,utf-8编码的文件
data = pd.read_csv('data.csv', encoding='utf-8')

写入文件:DataFrame的to_*方法

分析和处理完数据后,我们通常需要将结果保存到文件中。

Pandas的DataFrame对象拥有一系列 to_* 方法,可以方便地将数据写入不同格式的文件。

写入Excel文件:to_excel()

首先,我们创建一个简单的DataFrame作为示例。

import pandas as pd
data_to_write = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['A列','B列'])
data_to_write

to_excel()基本用法

将这个DataFrame写入名为 演示.xlsx 的文件中。

# 将DataFrame中的数据导入Excel工作簿
data_to_write.to_excel('演示.xlsx')

执行后,你会在当前目录下发现一个新生成的演示.xlsx文件。

to_excel()的重要参数:index

index=False可以阻止Pandas将DataFrame的索引写入文件。

这是to_excel()中最常用的参数之一。

data_to_write.to_excel('演示.xlsx', index=False)

to_excel()的其他参数

  • columns: 一个列表,用于指定要写入文件的列。
  • sheet_name: 字符串,指定写入的工作表的名称。

例如,只将 A列 的数据写入Excel,不包含行索引,并命名工作表为’MyData’:

data_to_write.to_excel('演示.xlsx', columns=['A列'], index=False, sheet_name='MyData')

写入CSV文件:to_csv()

与写入Excel类似,我们可以使用 to_csv() 方法将DataFrame保存为CSV文件。

data_to_write.to_csv('演示.csv')

to_csv()encoding参数:解决中文乱码的关键

当数据中包含中文字符时,直接用Excel打开CSV文件可能会出现乱码。

为了解决这个问题,建议使用 'utf_8_sig' 编码。它会在文件开头加入一个特殊标记(BOM),帮助Excel正确识别UTF-8编码。

data_to_write.to_csv('演示.csv', index=False, encoding='utf_8_sig')

实践案例:处理东方财富利润表数据

任务: 读取一个CSV格式的财务报表,并将其转换为Excel格式,以便于不熟悉编程的同事进行查阅。

案例要求

  1. 使用Pandas库读取 eastmoney.csv 文件。
  2. 将读取到的数据完整地写入到一个名为 eastmoney.xlsx 的Excel文件中。

步骤1:读取CSV文件

import pandas as pd
# 读取eastmoney.csv文件
data = pd.read_csv("eastmoney.csv")

步骤2:写入Excel文件

# 写入eastmoney.xlsx文件
data.to_excel("eastmoney.xlsx", index=False) # 通常建议加上 index=False

完整参考答案

Table 9: 东方财富利润表数据预览
import pandas as pd
data_eastmoney = pd.read_csv("eastmoney.csv")
data_eastmoney.to_excel("eastmoney.xlsx", index=False) # 写入时通常不保留Pandas的索引
print(data_eastmoney.head())

财务数据的读取与筛选

掌握了文件读写后,我们来学习一项至关重要的数据分析技能:数据筛选。

财务数据集通常非常庞大,我们往往只对其中一部分数据感兴趣。

“筛选”就是从庞大的数据集中精确地提取出我们所需要的信息子集的过程。

准备用于演示的样本数据

为了方便演示,我们首先创建一个3x3的DataFrame实例,并为其指定清晰的行和列标签。

Table 10: 用于数据筛选演示的样本DataFrame
import pandas as pd
data_filter_demo = pd.DataFrame([[1, 3, 3], [4, 5, 5], [7, 9, 9]], 
                                index=['r1', 'r2', 'r3'], 
                                columns=['c1', 'c2', 'c3'])
data_filter_demo

我们所有的筛选操作都将基于这个数据表进行。

筛选的基础:选取列

选取DataFrame中的一列或多列是最基本的操作。

选取单列(返回Series)

可以通过 df['列名'] 的方式实现。返回的结果是一个Pandas Series对象。

a = data_filter_demo['c1']
a

注意输出结果是一维的,保留了行索引,但没有二维表格结构。

选取单列(返回DataFrame)

如果你希望选取单列后仍然保持DataFrame的二维格式,可以在列名外再加一层方括号 df[['列名']]

Table 11: 以DataFrame格式选取单列 c1
b = data_filter_demo[['c1']]
b

选取多列

将一个包含多个列名的列表传递给方括号即可。

Table 12: 选取 c1 和 c3 两列
c = data_filter_demo[['c1', 'c3']]
c

筛选的基础:选取行

选取行的方式比选取列稍微复杂一些,Pandas为此提供了专门的索引器(indexer)。

选取行的两种模式:.iloc vs .loc

这是Pandas筛选操作中最核心、也最容易混淆的概念。

  • .iloc (Integer Location): 基于整数位置的索引。
  • .loc (Location): 基于标签名称的索引。

一个简单的记忆方法:i in iloc stands for “integer”。

使用.iloc按整数位置选取

.iloc总是把DataFrame看作一个从0开始计数的数组。

Table 13: 使用 .iloc 选取第2到3行(位置为1和2)
# 选取位置为1和2的行,不包含位置3
b = data_filter_demo.iloc[1:3]
b

.iloc的切片规则和Python列表一样,是“左闭右开”的。

使用.iloc选取单行

要选取单行,必须使用.iloc.loc

例如,选取最后一行(位置为-1):

c = data_filter_demo.iloc[-1]
c

如果直接使用 data[-1],程序会报错,因为它会尝试寻找一个名为-1

使用.loc按标签名称选取

.loc使用行索引的具体名称来选取。

Table 14: 使用 .loc 根据行标签选取
d = data_filter_demo.loc[['r2', 'r3']]
d

注意.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列。

Table 15: 使用 .loc 按标签名直接选取区块
d = data_filter_demo.loc[['r1', 'r2'], ['c1', 'c3']]
d

使用.iloc直接选取区块

按整数位置选取前2行(位置0,1)和第1、3列(位置0,2)。

Table 16: 使用 .iloc 按整数位置直接选取区块
e = data_filter_demo.iloc[0:2, [0, 2]]
e

两种方法得到了相同的结果,但其内部逻辑完全不同。

选取单个值

要精确地选取某行某列的单个值,推荐的写法是先用 .iloc.loc 定位到行,再用 [] 定位到列。

c = data_filter_demo.iloc[0]['c3'] # 或者 data_filter_demo.loc['r1']['c3']
c

更规范的写法是 data_filter_demo.at['r1', 'c3']data_filter_demo.iat[0, 2],但初学阶段掌握前一种即可。

按特定条件筛选

这是数据筛选中最强大的功能:根据数据本身的值来提取子集。

这通常被称为布尔索引 (Boolean Indexing)

布尔索引第一步:创建条件

我们首先创建一个布尔条件,例如 data_filter_demo['c1'] > 1

Pandas会逐行检查这个条件,返回一个由TrueFalse组成的Series。

condition = data_filter_demo['c1'] > 1
condition

这个True/False序列就是我们的“筛选器”或“掩码 (mask)”。

布尔索引第二步:应用筛选器

然后,我们将这个布尔Series传给DataFrame的 [] 中。

Pandas就会只返回那些在筛选器中对应值为True的行。

Table 17: 筛选 c1 列中数值大于1的行
# a = data_filter_demo[condition]
a = data_filter_demo[data_filter_demo['c1'] > 1]
a

复合条件筛选

当有多个筛选条件时,可以使用逻辑运算符 & (与) 和 | (或)。

关键规则: 每个独立的条件都必须用小括号 () 括起来。

复合条件示例

筛选 c1 列中数字大于1 且 (&) c2 列中数字小于8的行。

Table 18: 复合条件筛选
b = data_filter_demo[(data_filter_demo['c1'] > 1) & (data_filter_demo['c2'] < 8)]
b

只有 r2 行同时满足这两个条件,因此被筛选出来。

实践案例:读取与筛选东方财富利润表

任务: 对 eastmoney.xlsx 文件中的数据进行多项筛选操作。

案例要求

  1. 读取 eastmoney.xlsx 文件。
  2. 单独提取 “ann_date” (公告日期) 列。
  3. 提取第4行的数据(位置索引为3)。
  4. 使用 .iloc 提取第2行到第3行的数据。
  5. 筛选出所有 “ann_date” 等于 20230318 的记录。

任务1:提取”ann_date”列

import pandas as pd
data = pd.read_excel("eastmoney.xlsx")
print(data["ann_date"])

任务2:提取第4行数据

print(data.iloc[3])

任务3:提取第2到3行数据

print(data.iloc[1:3])

任务4:筛选”ann_date”等于20230318的数据

print(data[data["ann_date"] == 20230318])

数据表的拼接与合并

在复杂的商业分析中,数据往往分散在多个不同的表格或文件中。

要进行综合分析,就必须先把这些表连接起来。

Pandas提供了三大核心函数:merge(), concat()append()

准备用于演示的样本数据

我们创建两个简单的DataFrame,df1 包含公司评分,df2 包含公司股价。

import pandas as pd
df1 = pd.DataFrame({'公司': ['科大', '阿里', '百度'], '分数': [90, 95, 85]})
df2 = pd.DataFrame({'公司': ['科大', '阿里', '京东'], '股价': [20, 180, 30]})

print("df1 (公司评分):")
print(df1)
print("\ndf2 (公司股价):")
print(df2)

横向合并:merge()函数

merge() 函数类似于数据库操作中的 JOIN,它根据一个或多个共同的键 (key)将不同DataFrame的行连接起来。

这是进行关系型数据合并的首选方法。

merge类型1:内连接 (Inner Join)

merge() 默认执行内连接,只保留两个表中共同拥有的键。

内连接示意图 两个相交的圆圈,只有相交部分被填充,表示内连接只取交集。 内连接 (Inner Join)

内连接示例

Table 19: 默认的内连接 (Inner Join)
df3_inner = pd.merge(df1, df2) # on='公司' 是默认的
df3_inner

’百度’和’京东’因为不是共有的键,所以被丢弃了。

merge类型2:外连接 (Outer Join)

通过设置 how='outer',我们可以保留所有数据,即使它只出现在一个表中。

外连接示意图 两个相交的圆圈,两个圆的全部区域都被填充,表示外连接取并集。 外连接 (Outer Join)

外连接示例

Table 20: 外连接 (Outer Join)
df3_outer = pd.merge(df1, df2, how='outer')
df3_outer

对于缺失的数据(如’百度’的股价),Pandas会自动填充为 NaN (Not a Number)。

merge类型3:左连接 (Left Join)

通过设置 how='left',保留左边表(第一个参数df1)的所有行。

左连接示意图 两个相交的圆圈,左边的整个圆被填充,表示左连接保留左表全部数据。 左连接 (Left Join)

左连接示例

Table 21: 左连接 (Left Join)
df3_left = pd.merge(df1, df2, how='left')
df3_left

df1中所有的公司都被保留,而df2中独有的’京东’被舍弃。右连接 (how='right') 则反之。

纵向拼接:concat()函数

concat() 函数用于将多个DataFrame沿着一个轴(行或列)进行拼接,类似于将几张表格头尾相连地“粘”在一起。

concat按行拼接 (默认)

这是最常见的用法,即把一个DataFrame堆叠在另一个的下方 (axis=0)。

Table 22: 按行方向进行纵向拼接
# 默认情况下,axis=0,按行方向进行连接。
df3_concat0 = pd.concat([df1,df2], axis=0)
df3_concat0

不同表的列被合并,缺失的位置用NaN填充。注意,原始的行索引被保留了。

concat处理索引:ignore_index=True

如果想生成一套新的连续索引,可以添加参数 ignore_index=True

df3_concat_reindex = pd.concat([df1,df2], axis=0, ignore_index=True)
df3_concat_reindex

concat按列拼接

通过设置 axis=1concat() 也可以将DataFrame左右拼接起来。

Table 23: 按列方向进行横向拼接
df3_concat1 = pd.concat([df1,df2],axis=1)
df3_concat1

效果是根据行索引将两个表并排放在一起。

快速追加:append()函数 (即将弃用)

append() 方法可以看作是 concat(axis=0) 的一个简化版。

Table 24: 使用 append() 追加一个DataFrame
# df3_append = df1.append(df2) # 旧语法
df3_append = pd.concat([df1, df2]) # 推荐的新语法
df3_append

函数即将弃用 (Deprecation Warning)

Pandas的 .append() 方法已被标记为不推荐使用 (deprecated)。官方推荐使用 pd.concat() 来替代。在我们的新项目中,应当养成使用 pd.concat() 的好习惯。

实践案例:合并贵州茅台两年利润表

任务: 将贵州茅台2019年和2020年的利润表数据(两个Excel文件)合并成一个单一的数据集。

案例要求

  1. 分别读取 贵州茅台_利润表_2019年.xlsx2020年.xlsx
  2. 使用 concat 函数按行方向将两个DataFrame拼接起来,并忽略原索引。
  3. 使用 append 函数实现相同的效果 (作为对比)。
  4. 打印结果进行验证。

完整参考答案

Table 25: 合并后的贵州茅台2019-2020年利润表数据
# 准备环境
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)

综合实战案例

现在,是时候将这些独立的技能串联起来,解决更接近真实世界的商业分析问题了。

本节将通过两个递进的实战案例,带领大家完成一个完整的小型数据处理流程。

实战一:贵州茅台2023年年报关键指标提取

任务: 从Tushare获取贵州茅台最新的利润表数据,提取2023年年报中的关键财务指标,并进行数据去重。

案例要求

  1. 获取数据: 使用Tushare的 income 接口获取贵州茅台的所有利润表数据。
  2. 筛选: 筛选出 end_date 为 “20231231” 的数据。
  3. 选取: 只选取 'ts_code', 'end_date', 'total_revenue', 'total_cogs', 'n_income', 'ebit' 这几个关键列。
  4. 去重: 根据 end_date 列去除重复的报告期数据。

步骤1:获取与筛选

获取数据后,使用布尔索引筛选出2023年年报。

import tushare as ts
pro = ts.pro_api("ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474")
df = pro.income(ts_code='600519.SH')

# 任务1: 筛选2023年12月31日的数据
df_1 = df[df["end_date"]=="20231231"]

步骤2:选取列

通过传入列名列表,选取我们关心的关键指标。

# 任务2: 筛选指定列
df_2 = df[['ts_code','end_date','total_revenue','total_cogs','n_income','ebit']]

步骤3:去重

使用drop_duplicates()方法,确保每个报告期只有一条最新的记录。

# 任务3: 根据季报日期去重
df_3 = df.drop_duplicates("end_date")

完整参考答案

# 为保证可复现,此处使用模拟数据
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())

实战二:批量获取白酒三巨头五年财务报表

任务: 批量获取贵州茅台、五粮液、泸州老窖过去五年的三大财务报表,进行数据处理后结构化存储。

这是一个更复杂的、接近真实工作流的案例。

案例要求概览

  1. 批量获取: 编写函数,循环获取多家公司、多年份的三大报表。
  2. 数据拼接: 将每年的报表拼接成五年总表。
  3. 列名汉化: 将英文列名转换为中文,提高可读性。
  4. 结构化存储: 将处理好的数据存入多个Excel文件,每个文件包含多个Sheet。
  5. 验证: 读取生成的文件进行检查。

核心技术点1:列名重命名

.rename()函数可以接收一个字典,将旧的列名映射到新的列名。

语法: df.rename(columns={'旧名1': '新名1', '旧名2': '新名2'})

我们将编写一个辅助函数来完成这个任务。

核心技术点2:写入多个Sheet

pd.ExcelWriter是向同一个Excel文件写入多个Sheet的标准方法。

流程:

  1. 创建writer对象: writer = pd.ExcelWriter('文件名.xlsx')
  2. 多次写入: df1.to_excel(writer, sheet_name='表1'), df2.to_excel(writer, sheet_name='表2')
  3. 保存文件: writer.save() (新版pandas中writer.close()也可以)

步骤1:列名汉化辅助函数

这个函数读取一个预定义的Excel文件(包含中英文名对应关系),创建一个’英-中’映射字典,然后用.rename()方法重命名DataFrame的列。

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

步骤2:数据获取与存储主函数(逻辑拆解)

  1. 外层循环遍历公司列表
  2. 内层循环遍历年份列表
  3. 在内层循环中,获取当年的三张报表。
  4. 将获取的当年报表,appendconcat到该公司的总表DataFrame上。
  5. 内层循环结束后,调用rename_col汉化总表。
  6. 使用ExcelWriter将三张汉化后的总表写入一个以公司名命名的Excel文件中。

步骤3:函数调用与验证

定义好公司、代码和年份列表后,直接调用主函数即可。

最后,读取生成的一个文件(如“贵州茅台.xlsx”)中的某个Sheet,打印其内容,以验证整个流程是否正确执行。

完整参考答案

Table 26: 验证结果:贵州茅台2019-2023年资产负债表(模拟数据)
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数据处理的核心技能:

  • 创建DataFrame: 从字典、Numpy数组等多种源。
  • 文件I/O: 轻松读写Excel和CSV文件。
  • 数据筛选: 使用.loc, .iloc和布尔索引精确定位数据。
  • 数据合并: 使用mergeconcat整合多源数据。

这些是进行任何复杂数据分析的基石。

未来展望

掌握了基础操作后,你将能够探索Pandas更高级的功能:

  • 更复杂的groupby聚合操作。
  • 强大的时间序列数据处理能力。
  • 结合MatplotlibSeaborn进行数据可视化。
  • 性能优化技巧,处理亿级数据。

数据分析的旅程才刚刚开始,祝大家学有所成!

Q & A

提问环节