4  pandas数据框

点击下载本章节代码

4.1 Pandas处理表格数据

参考: https://realpython.com/pandas-dataframe/

pandas DataFrame是一种包含二维数据及其对应标签数据结构

DataFrame 类似于Excel 或 SQL 中使用的电子表格。在很多情况下,DataFrame 比表格或电子表格更快、更易于使用且功能更强大。

安装所需包

%pip install pandas openpyxl pyarrow dfply matplotlib seaborn --upgrade

导入pandas

import pandas as pd

创建示例数据

让我们创建一个会计科目表作为示例:

# 创建会计科目数据
data = {
     'account_name': ['现金', '银行存款', '应收账款', '存货', '固定资产', '应付账款', '实收资本'],
     'account_type': ['资产', '资产', '资产', '资产', '资产', '负债', '所有者权益'],
     'balance': [50000.0, 200000.0, 150000.0, 180000.0, 300000.0, 100000.0, 680000.0],
     'balance_direction': ['借', '借', '借', '借', '借', '贷', '贷']
}

# 使用会计科目编号作为索引
account_codes = [1001, 1002, 1122, 1123, 1601, 2202, 4001]

df = pd.DataFrame(data, index = account_codes)
Tip

会计背景说明: - 账户编号: 通常使用数字编码,如1开头表示资产,2开头表示负债等 - 借贷方向: 资产类账户余额在借方,负债和所有者权益账户余额在贷方 - 余额: 表示该科目的期末余额

4.2 DataFrame结构与属性

查看DataFrame类型和结构:

type(df)
pandas.core.frame.DataFrame
df

Note

可以删掉 labels 的信息,系统会自己给一个编号。

索引(index)

df.index
Index([1001, 1002, 1122, 1123, 1601, 2202, 4001], dtype='int64')

列名(columns)

df.columns
Index(['account_name', 'account_type', 'balance', 'balance_direction'], dtype='object')

值(values)

df.values
array([['现金', '资产', 50000.0, '借'],
       ['银行存款', '资产', 200000.0, '借'],
       ['应收账款', '资产', 150000.0, '借'],
       ['存货', '资产', 180000.0, '借'],
       ['固定资产', '资产', 300000.0, '借'],
       ['应付账款', '负债', 100000.0, '贷'],
       ['实收资本', '所有者权益', 680000.0, '贷']], dtype=object)

形状(shape)

df.shape
(7, 4)

数据类型(dtypes)

df.dtypes
account_name          object
account_type          object
balance              float64
balance_direction     object
dtype: object

基本信息(info)

df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 7 entries, 1001 to 4001
Data columns (total 4 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   account_name       7 non-null      object 
 1   account_type       7 non-null      object 
 2   balance            7 non-null      float64
 3   balance_direction  7 non-null      object 
dtypes: float64(1), object(3)
memory usage: 280.0+ bytes

4.3 DataFrame 行列操作

访问列

通过列名访问列数据:

# 显示账户名称列
df.account_name
# 将账户名称列赋值给变量
accounts = df['account_name']
accounts
1001      现金
1002    银行存款
1122    应收账款
1123      存货
1601    固定资产
2202    应付账款
4001    实收资本
Name: account_name, dtype: object

查看列的类型:

type(df.account_name)
pandas.core.series.Series

访问特定位置的值:

# 访问账户编号为1002的账户名称
accounts[1002]
'银行存款'
Note

说明: DataFrame的每一列都是一个Series对象。Series是pandas的一维数组,类似于Excel中的单独一列。

访问行

使用.loc访问行:

# 访问账户编号为1122的科目信息
account_row = df.loc[1122]
account_row
account_name             应收账款
account_type               资产
balance              150000.0
balance_direction           借
Name: 1122, dtype: object

访问行中的特定列:

# 获取该科目的账户类型
account_row['account_type']
'资产'

查看行的类型:

type(account_row)
pandas.core.series.Series
Note

说明: 访问DataFrame的一行会返回一个Series对象,其索引是原DataFrame的列名。

转置

原始数据:

df
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

转置后:

df.T
1001 1002 1122 1123 1601 2202 4001
account_name 现金 银行存款 应收账款 存货 固定资产 应付账款 实收资本
account_type 资产 资产 资产 资产 资产 负债 所有者权益
balance 50000.0 200000.0 150000.0 180000.0 300000.0 100000.0 680000.0
balance_direction

查看头尾数据

查看前n行:

df.head(n=3)
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0

查看后n行(默认5行):

df.tail()
account_name account_type balance balance_direction
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

创建 DataFrame

从字典列表创建:

l = [{'x': 1, 'y': 2, 'z': 100},
     {'x': 2, 'y': 4, 'z': 100},
     {'x': 3, 'y': 8, 'z': 100}]

pd.DataFrame(l)
x y z
0 1 2 100
1 2 4 100
2 3 8 100

从二维数组创建 - 指定列名和索引:

# 从数组构建 DataFrame
data = [['Alice', 25], ['Bob', 30], ['Charlie', 35]]
pd.DataFrame(data, columns=['Name', 'Age'], index = [1,2,9])
Name Age
1 Alice 25
2 Bob 30
9 Charlie 35

使用默认列名和索引:

pd.DataFrame(data)
0 1
0 Alice 25
1 Bob 30
2 Charlie 35

4.4 DataFrame 导入与导出

学习目标: - 掌握从不同文件格式读取数据 - 了解常见数据导入问题的处理方法 - 学会将处理后的数据导出为专业格式

pandas可以将各种数据格式导入为DataFrame:

读取CSV文件

CSV(逗号分隔值)是会计数据最常见的格式之一:

# 基本读取
df = pd.read_csv('financial_data.csv')

# 指定编码(处理中文)
df = pd.read_csv('financial_data.csv', encoding='utf-8')

# 指定数据类型(提高性能)
df = pd.read_csv('financial_data.csv',
                 dtype={'account_number': str, 'amount': float})

# 处理日期列
df = pd.read_csv('financial_data.csv',
                 parse_dates=['transaction_date'])
Tip

会计应用: 从ERP系统或财务软件导出的数据通常是CSV格式。正确设置编码和数据类型可以避免很多后续问题。

读取Excel文件

# 读取第一个工作表
df = pd.read_excel("example.xlsx", sheet_name=0)
# 读取特定工作表
df = pd.read_excel('financial_statements.xlsx', sheet_name='Balance Sheet')

# 跳过某些行(如标题行)
df = pd.read_excel('financial_statements.xlsx', skiprows=[0, 1])

# 指定索引列
df = pd.read_excel('financial_statements.xlsx', index_col=0)

处理大文件

对于大型财务数据文件,可以分块读取:

# 分块读取CSV
chunk_size = 10000
chunks = []
for chunk in pd.read_csv('large_transaction_file.csv', chunksize=chunk_size):
    # 对每个块进行处理
    chunks.append(chunk)
df = pd.concat(chunks, ignore_index=True)

导出数据

导出到Excel:

df.to_excel("example.xlsx", index=False)
# 导出到CSV(不包含索引)
df.to_csv('output.csv', index=False, encoding='utf-8-sig')

# 导出到Excel with 格式化
with pd.ExcelWriter('financial_report.xlsx', engine='openpyxl') as writer:
    df.to_excel(writer, sheet_name='Summary', index=False)
Warning

常见错误: 忘记设置 index=False 会导致导出时包含行号列,这在财务报表中通常是不需要的。

错误处理

# 安全地读取文件
try:
    df = pd.read_excel('financial_data.xlsx')
    print(f"成功读取 {len(df)} 行数据")
except FileNotFoundError:
    print("文件未找到,请检查文件路径")
except Exception as e:
    print(f"读取文件时出错: {e}")

4.5 数据清洗(Data Cleaning)

学习目标: - 识别和处理缺失值 - 检测和删除重复数据 - 转换数据类型 - 处理会计数据中的特殊格式

Important

为什么数据清洗很重要? 会计数据常常来自不同系统,可能包含缺失值、重复记录、格式不统一等问题。数据清洗是数据分析的第一步,决定了分析结果的准确性。

检查缺失值

创建示例财务数据(包含缺失值):

import numpy as np

# 创建包含缺失值的账目数据
accounts_data = {
    'account_number': ['1001', '1002', '1003', '1004', '1005'],
    'account_name': ['现金', '应收账款', '存货', None, '固定资产'],
    'amount': [50000, 120000, np.nan, 80000, 200000],
    'category': ['资产', '资产', '资产', '资产', None]
}
accounts_df = pd.DataFrame(accounts_data)
accounts_df
account_number account_name amount category
0 1001 现金 50000.0 资产
1 1002 应收账款 120000.0 资产
2 1003 存货 NaN 资产
3 1004 None 80000.0 资产
4 1005 固定资产 200000.0 None

检查缺失值:

# 检查每列的缺失值数量
accounts_df.isnull().sum()
account_number    0
account_name      1
amount            1
category          1
dtype: int64
# 检查是否有任何缺失值
accounts_df.isnull().any()
account_number    False
account_name       True
amount             True
category           True
dtype: bool
# 查看包含缺失值的行
accounts_df[accounts_df.isnull().any(axis=1)]
account_number account_name amount category
2 1003 存货 NaN 资产
3 1004 None 80000.0 资产
4 1005 固定资产 200000.0 None

处理缺失值

不同的处理方法:

# 方法1: 删除包含缺失值的行
df_dropped = accounts_df.dropna()
print(f"删除后剩余 {len(df_dropped)} 行")
df_dropped
删除后剩余 2 行
account_number account_name amount category
0 1001 现金 50000.0 资产
1 1002 应收账款 120000.0 资产
# 方法2: 用特定值填充(金额用0填充)
df_filled = accounts_df.copy()
df_filled['amount'] = df_filled['amount'].fillna(0)
df_filled
account_number account_name amount category
0 1001 现金 50000.0 资产
1 1002 应收账款 120000.0 资产
2 1003 存货 0.0 资产
3 1004 None 80000.0 资产
4 1005 固定资产 200000.0 None
# 方法3: 用平均值填充(适用于数值列)
df_mean = accounts_df.copy()
df_mean['amount'] = df_mean['amount'].fillna(accounts_df['amount'].mean())
df_mean
account_number account_name amount category
0 1001 现金 50000.0 资产
1 1002 应收账款 120000.0 资产
2 1003 存货 112500.0 资产
3 1004 None 80000.0 资产
4 1005 固定资产 200000.0 None
# 方法4: 用前一个值填充(forward fill)
df_ffill = accounts_df.copy()
df_ffill = df_ffill.fillna(method='ffill')
df_ffill
account_number account_name amount category
0 1001 现金 50000.0 资产
1 1002 应收账款 120000.0 资产
2 1003 存货 120000.0 资产
3 1004 存货 80000.0 资产
4 1005 固定资产 200000.0 资产
Tip

会计应用: 对于财务金额,通常用0填充缺失值。对于分类数据(如账户类别),可能需要用”未分类”或最频繁的类别填充。

检测重复数据

创建包含重复记录的交易数据:

# 创建包含重复的交易数据
transactions = pd.DataFrame({
    'transaction_id': ['T001', 'T002', 'T003', 'T002', 'T004'],
    'date': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-02', '2024-01-04'],
    'amount': [1000, 2000, 1500, 2000, 3000],
    'description': ['销售收入', '采购支出', '销售收入', '采购支出', '销售收入']
})
transactions
transaction_id date amount description
0 T001 2024-01-01 1000 销售收入
1 T002 2024-01-02 2000 采购支出
2 T003 2024-01-03 1500 销售收入
3 T002 2024-01-02 2000 采购支出
4 T004 2024-01-04 3000 销售收入

检测重复:

# 检查完全重复的行
transactions.duplicated()
0    False
1    False
2    False
3     True
4    False
dtype: bool
# 查看重复的行
transactions[transactions.duplicated(keep=False)]
transaction_id date amount description
1 T002 2024-01-02 2000 采购支出
3 T002 2024-01-02 2000 采购支出
# 基于特定列检查重复(交易ID应该是唯一的)
transactions.duplicated(subset=['transaction_id'])
0    False
1    False
2    False
3     True
4    False
dtype: bool

删除重复:

# 删除重复行,保留第一次出现的
transactions_clean = transactions.drop_duplicates(subset=['transaction_id'], keep='first')
transactions_clean
transaction_id date amount description
0 T001 2024-01-01 1000 销售收入
1 T002 2024-01-02 2000 采购支出
2 T003 2024-01-03 1500 销售收入
4 T004 2024-01-04 3000 销售收入
Warning

常见错误: 删除重复数据前,要确认哪些字段组合应该是唯一的。有时看似重复的记录可能是合法的(如相同客户的多次交易)。

数据类型转换

创建需要类型转换的数据:

# 创建混合类型的财务数据
messy_data = pd.DataFrame({
    'account': ['1001', '1002', '1003', '1004'],
    'revenue': ['$1,200', '$2,500', '$1,800', '$3,200'],
    'cost': ['(500)', '1200', '(800)', '1500'],
    'quantity': ['100', '200', '150', '250']
})
messy_data
account revenue cost quantity
0 1001 $1,200 (500) 100
1 1002 $2,500 1200 200
2 1003 $1,800 (800) 150
3 1004 $3,200 1500 250
# 查看当前数据类型
messy_data.dtypes
account     object
revenue     object
cost        object
quantity    object
dtype: object

处理货币符号和逗号:

# 清理revenue列(移除$和逗号)
messy_data['revenue_clean'] = messy_data['revenue'].str.replace('$', '').str.replace(',', '').astype(float)
messy_data
account revenue cost quantity revenue_clean
0 1001 $1,200 (500) 100 1200.0
1 1002 $2,500 1200 200 2500.0
2 1003 $1,800 (800) 150 1800.0
3 1004 $3,200 1500 250 3200.0

处理括号表示的负数(会计中常用):

# 处理括号表示的负数
def parse_accounting_number(value):
    value = str(value).strip()
    if '(' in value:
        # 移除括号并转为负数
        return -float(value.replace('(', '').replace(')', ''))
    else:
        return float(value)

messy_data['cost_clean'] = messy_data['cost'].apply(parse_accounting_number)
messy_data[['cost', 'cost_clean']]
cost cost_clean
0 (500) -500.0
1 1200 1200.0
2 (800) -800.0
3 1500 1500.0

转换为数值类型:

# 转换quantity为整数
messy_data['quantity'] = pd.to_numeric(messy_data['quantity'], errors='coerce')
messy_data.dtypes
account           object
revenue           object
cost              object
quantity           int64
revenue_clean    float64
cost_clean       float64
dtype: object
Tip

会计应用: - 货币金额常包含货币符号($, ¥)和千位分隔符 - 负数可能用括号表示,如 (1,000) 表示 -1000 - 账户编号应保持为字符串类型,避免前导零丢失

数据验证

创建需要验证的账目数据:

# 创建试算平衡表数据
trial_balance = pd.DataFrame({
    'account': ['现金', '应收账款', '应付账款', '资本'],
    'debit': [10000, 5000, 0, 0],
    'credit': [0, 0, 3000, 12000]
})
trial_balance
account debit credit
0 现金 10000 0
1 应收账款 5000 0
2 应付账款 0 3000
3 资本 0 12000

验证借贷平衡:

# 计算借贷总额
total_debit = trial_balance['debit'].sum()
total_credit = trial_balance['credit'].sum()

print(f"借方总额: ¥{total_debit:,.2f}")
print(f"贷方总额: ¥{total_credit:,.2f}")

# 验证是否平衡
if total_debit == total_credit:
    print("✓ 借贷平衡")
else:
    print(f"✗ 不平衡,差额: ¥{abs(total_debit - total_credit):,.2f}")
借方总额: ¥15,000.00
贷方总额: ¥15,000.00
✓ 借贷平衡

检查异常值:

# 创建包含异常值的销售数据
sales_data = pd.DataFrame({
    'product': ['A', 'B', 'C', 'D', 'E'],
    'units_sold': [100, 150, 200, 1000000, 180],  # D产品的数量明显异常
    'unit_price': [10, 15, 12, 11, 13]
})

# 使用describe()查看统计信息
sales_data['units_sold'].describe()
count          5.000000
mean      200126.000000
std       447143.160945
min          100.000000
25%          150.000000
50%          180.000000
75%          200.000000
max      1000000.000000
Name: units_sold, dtype: float64
# 识别异常值(使用IQR方法)
Q1 = sales_data['units_sold'].quantile(0.25)
Q3 = sales_data['units_sold'].quantile(0.75)
IQR = Q3 - Q1

# 定义异常值范围
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 找出异常值
outliers = sales_data[(sales_data['units_sold'] < lower_bound) |
                      (sales_data['units_sold'] > upper_bound)]
outliers
product units_sold unit_price
3 D 1000000 11
Note

练习: 1. 创建一个包含缺失值的应收账款数据集 2. 检查并处理缺失的金额和客户名称 3. 验证没有重复的发票号 4. 确保所有金额都是正数

4.6 DataFrame 筛选与条件过滤

学习目标: - 掌握多种数据筛选方法 - 学会条件过滤 - 应用于财务数据查询

切片操作

像list和string一样切片行数据:

# 前3行数据
df[:3]
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0

通过Label(索引)切片:

# 筛选账户编号1001到1123的科目
df.loc[1001:1123]
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0

同时筛选行和列:

# 只查看特定科目的名称和余额
df.loc[1001:1123, ['account_name','balance']]
account_name balance
1001 现金 50000.0
1002 银行存款 200000.0
1122 应收账款 150000.0
1123 存货 180000.0

通过位置(position)筛选:

# 前3行,选择第1,3,2,4列(按位置)
df.iloc[:3, [0,2,1,3]]
account_name balance account_type balance_direction
1001 现金 50000.0 资产
1002 银行存款 200000.0 资产
1122 应收账款 150000.0 资产

访问单个元素:

# 访问第一行第二列的值
df.iat[0, 1]
'资产'

条件筛选

筛选余额大于150000的科目:

# 筛选高额余额账户
high_balance_accounts = df[df['balance'] > 150000]
high_balance_accounts
account_name account_type balance balance_direction
1002 银行存款 资产 200000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
4001 实收资本 所有者权益 680000.0

筛选资产类科目:

# 筛选所有资产类账户
asset_accounts = df[df['account_type'] == '资产']
asset_accounts
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0

多条件筛选(使用&和|):

# 筛选余额大于100000且为资产类的科目
df[(df['balance'] > 100000) & (df['account_type'] == '资产')]
account_name account_type balance balance_direction
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
# 筛选负债类或所有者权益类科目
df[(df['account_type'] == '负债') | (df['account_type'] == '所有者权益')]
account_name account_type balance balance_direction
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0
Warning

注意: - 使用 & 表示”且”(AND) - 使用 | 表示”或”(OR) - 每个条件必须用括号括起来 - 不能使用Python的 andor 关键字

使用isin筛选

筛选特定账户:

# 筛选现金和银行存款账户
df[df['account_name'].isin(['现金', '银行存款'])]
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0

排除特定账户(使用~取反):

# 筛选除现金外的所有账户
df[~df['account_name'].isin(['现金'])]
account_name account_type balance balance_direction
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

query()方法

更简洁的筛选方式:

# 使用query方法筛选
df.query('balance > 150000')
account_name account_type balance balance_direction
1002 银行存款 资产 200000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
4001 实收资本 所有者权益 680000.0
# 复杂条件查询
df.query('account_type == "资产" and balance > 100000')
account_name account_type balance balance_direction
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
Tip

会计应用: 条件筛选常用于: - 筛选特定类别的科目(资产、负债等) - 查找大额交易或余额 - 识别异常账户 - 生成特定范围的财务报表

4.7 数据操作

赋值

首先创建副本:

df2 = df.copy()
df2
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

修改单个值和批量赋值:

# 修改单个值
df2.iat[0, 0] = "库存现金"

# 批量修改特定行的某列值
df2.loc[1001:1123, 'balance'] = [55000, 210000, 160000, 190000]

# 创建新列(余额的万元单位)
df2['balance_万元'] = df2['balance'] / 10000
df2
account_name account_type balance balance_direction balance_万元
1001 库存现金 资产 55000.0 5.5
1002 银行存款 资产 210000.0 21.0
1122 应收账款 资产 160000.0 16.0
1123 存货 资产 190000.0 19.0
1601 固定资产 资产 300000.0 30.0
2202 应付账款 负债 100000.0 10.0
4001 实收资本 所有者权益 680000.0 68.0

排序

按索引排序:

# 按账户编号(索引)排序
df.sort_index()
account_name account_type balance balance_direction
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1123 存货 资产 180000.0
1601 固定资产 资产 300000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

按值排序

# 按余额排序
df.sort_values('balance', ascending=False)
account_name account_type balance balance_direction
4001 实收资本 所有者权益 680000.0
1601 固定资产 资产 300000.0
1002 银行存款 资产 200000.0
1123 存货 资产 180000.0
1122 应收账款 资产 150000.0
2202 应付账款 负债 100000.0
1001 现金 资产 50000.0

重命名

批量重命名列名:

使用列表推导式:

df2 = df.copy()

df2.columns = [x.upper() for x in df.columns]
df2.head(2)
ACCOUNT_NAME ACCOUNT_TYPE BALANCE BALANCE_DIRECTION
1001 现金 资产 50000.0
1002 银行存款 资产 200000.0

选择性重命名

使用rename方法:

df2 = df.copy()
df2 = df2.rename(columns={"account_name": "科目名称", "balance": "余额"},
                 index={1001: "现金科目"})
df2.head(2)
科目名称 account_type 余额 balance_direction
现金科目 现金 资产 50000.0
1002 银行存款 资产 200000.0

删除行/列

删除行:

# 删除特定账户编号的行
df2 = df.drop(index=[1001, 1123])
df2
account_name account_type balance balance_direction
1002 银行存款 资产 200000.0
1122 应收账款 资产 150000.0
1601 固定资产 资产 300000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

删除列

# 删除特定列
df2 = df.drop(columns=['balance_direction', 'account_type'])
df2
account_name balance
1001 现金 50000.0
1002 银行存款 200000.0
1122 应收账款 150000.0
1123 存货 180000.0
1601 固定资产 300000.0
2202 应付账款 100000.0
4001 实收资本 680000.0

4.8 统计计算与自定义函数

统计量

均值:

# 计算余额的平均值
df['balance'].mean()
np.float64(237142.85714285713)

标准差

# 计算余额的标准差
df['balance'].std()
210611.3550052391

描述性统计

# 余额的描述性统计
df['balance'].describe()
count         7.000000
mean     237142.857143
std      210611.355005
min       50000.000000
25%      125000.000000
50%      180000.000000
75%      250000.000000
max      680000.000000
Name: balance, dtype: float64

财务计算

学习目标: - 掌握累计计算(running totals) - 学会计算百分比变化 - 了解移动平均和滚动计算 - 应用财务比率分析

累计求和(Running Totals)

创建月度收入数据:

# 创建月度收入数据
monthly_revenue = pd.DataFrame({
    'month': ['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06'],
    'revenue': [100000, 120000, 115000, 135000, 140000, 150000],
    'cost': [60000, 70000, 68000, 80000, 82000, 88000]
})
monthly_revenue
month revenue cost
0 2024-01 100000 60000
1 2024-02 120000 70000
2 2024-03 115000 68000
3 2024-04 135000 80000
4 2024-05 140000 82000
5 2024-06 150000 88000

计算累计收入(年初至今YTD):

# 计算累计收入
monthly_revenue['累计收入'] = monthly_revenue['revenue'].cumsum()
monthly_revenue
month revenue cost 累计收入
0 2024-01 100000 60000 100000
1 2024-02 120000 70000 220000
2 2024-03 115000 68000 335000
3 2024-04 135000 80000 470000
4 2024-05 140000 82000 610000
5 2024-06 150000 88000 760000

计算累计利润:

# 先计算每月利润
monthly_revenue['profit'] = monthly_revenue['revenue'] - monthly_revenue['cost']
# 再计算累计利润
monthly_revenue['累计利润'] = monthly_revenue['profit'].cumsum()
monthly_revenue[['month', 'profit', '累计利润']]
month profit 累计利润
0 2024-01 40000 40000
1 2024-02 50000 90000
2 2024-03 47000 137000
3 2024-04 55000 192000
4 2024-05 58000 250000
5 2024-06 62000 312000
Tip

会计应用: 累计求和常用于: - 年初至今(YTD)收入/支出 - 现金流量表中的期末余额 - 累计折旧计算

百分比计算

计算环比增长率(Month-over-Month):

# 计算环比增长率
monthly_revenue['环比增长率'] = monthly_revenue['revenue'].pct_change() * 100
monthly_revenue[['month', 'revenue', '环比增长率']]
month revenue 环比增长率
0 2024-01 100000 NaN
1 2024-02 120000 20.000000
2 2024-03 115000 -4.166667
3 2024-04 135000 17.391304
4 2024-05 140000 3.703704
5 2024-06 150000 7.142857

计算同比增长率(需要上年同期数据):

# 创建两年数据
two_years_data = pd.DataFrame({
    'month': ['2023-01', '2023-02', '2023-03', '2024-01', '2024-02', '2024-03'],
    'revenue': [80000, 90000, 85000, 100000, 120000, 115000]
})

# 计算同比增长率(与3个月前比较,假设季度数据)
# 实际应用中通常是12个月
two_years_data['同比增长率'] = two_years_data['revenue'].pct_change(periods=3) * 100
two_years_data
month revenue 同比增长率
0 2023-01 80000 NaN
1 2023-02 90000 NaN
2 2023-03 85000 NaN
3 2024-01 100000 25.000000
4 2024-02 120000 33.333333
5 2024-03 115000 35.294118

计算占比(Percentage of Total):

# 创建部门收入数据
dept_revenue = pd.DataFrame({
    'department': ['销售部', '服务部', '咨询部', '培训部'],
    'revenue': [500000, 300000, 150000, 50000]
})

# 计算各部门收入占比
total_revenue = dept_revenue['revenue'].sum()
dept_revenue['收入占比%'] = (dept_revenue['revenue'] / total_revenue * 100).round(2)
dept_revenue
department revenue 收入占比%
0 销售部 500000 50.0
1 服务部 300000 30.0
2 咨询部 150000 15.0
3 培训部 50000 5.0

移动平均(Moving Average)

计算3个月移动平均:

# 创建更长时间序列
sales_data = pd.DataFrame({
    'month': pd.date_range('2024-01', periods=12, freq='MS'),
    'sales': [100, 110, 105, 120, 115, 125, 130, 135, 128, 140, 145, 150]
})

# 计算3个月移动平均
sales_data['MA_3'] = sales_data['sales'].rolling(window=3).mean()
sales_data[['month', 'sales', 'MA_3']]
month sales MA_3
0 2024-01-01 100 NaN
1 2024-02-01 110 NaN
2 2024-03-01 105 105.000000
3 2024-04-01 120 111.666667
4 2024-05-01 115 113.333333
5 2024-06-01 125 120.000000
6 2024-07-01 130 123.333333
7 2024-08-01 135 130.000000
8 2024-09-01 128 131.000000
9 2024-10-01 140 134.333333
10 2024-11-01 145 137.666667
11 2024-12-01 150 145.000000
Tip

会计应用: 移动平均可以: - 平滑季节性波动 - 识别长期趋势 - 预测未来表现

财务比率计算

创建财务报表数据:

# 创建资产负债表和利润表数据
financial_data = pd.DataFrame({
    'period': ['Q1', 'Q2', 'Q3', 'Q4'],
    'current_assets': [500000, 520000, 480000, 550000],
    'current_liabilities': [300000, 310000, 290000, 320000],
    'total_assets': [1000000, 1050000, 1020000, 1100000],
    'total_liabilities': [600000, 620000, 610000, 650000],
    'revenue': [400000, 450000, 420000, 480000],
    'net_income': [50000, 60000, 55000, 70000]
})
financial_data
period current_assets current_liabilities total_assets total_liabilities revenue net_income
0 Q1 500000 300000 1000000 600000 400000 50000
1 Q2 520000 310000 1050000 620000 450000 60000
2 Q3 480000 290000 1020000 610000 420000 55000
3 Q4 550000 320000 1100000 650000 480000 70000

计算流动比率(Current Ratio):

# 流动比率 = 流动资产 / 流动负债
financial_data['流动比率'] = (financial_data['current_assets'] /
                            financial_data['current_liabilities']).round(2)
financial_data[['period', '流动比率']]
period 流动比率
0 Q1 1.67
1 Q2 1.68
2 Q3 1.66
3 Q4 1.72

计算资产负债率(Debt-to-Asset Ratio):

# 资产负债率 = 总负债 / 总资产
financial_data['资产负债率%'] = (financial_data['total_liabilities'] /
                                financial_data['total_assets'] * 100).round(2)
financial_data[['period', '资产负债率%']]
period 资产负债率%
0 Q1 60.00
1 Q2 59.05
2 Q3 59.80
3 Q4 59.09

计算净利润率(Net Profit Margin):

# 净利润率 = 净利润 / 营业收入
financial_data['净利润率%'] = (financial_data['net_income'] /
                              financial_data['revenue'] * 100).round(2)
financial_data[['period', 'revenue', 'net_income', '净利润率%']]
period revenue net_income 净利润率%
0 Q1 400000 50000 12.50
1 Q2 450000 60000 13.33
2 Q3 420000 55000 13.10
3 Q4 480000 70000 14.58

现金流量计算

创建现金流量数据:

# 创建现金流量数据
cash_flow = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=10, freq='D'),
    'cash_in': [50000, 0, 30000, 45000, 0, 60000, 0, 40000, 55000, 0],
    'cash_out': [20000, 15000, 25000, 10000, 30000, 20000, 15000, 25000, 20000, 18000]
})

# 计算每日净现金流
cash_flow['net_cash_flow'] = cash_flow['cash_in'] - cash_flow['cash_out']

# 计算现金余额(假设期初余额为100000)
opening_balance = 100000
cash_flow['cash_balance'] = opening_balance + cash_flow['net_cash_flow'].cumsum()

cash_flow
date cash_in cash_out net_cash_flow cash_balance
0 2024-01-01 50000 20000 30000 130000
1 2024-01-02 0 15000 -15000 115000
2 2024-01-03 30000 25000 5000 120000
3 2024-01-04 45000 10000 35000 155000
4 2024-01-05 0 30000 -30000 125000
5 2024-01-06 60000 20000 40000 165000
6 2024-01-07 0 15000 -15000 150000
7 2024-01-08 40000 25000 15000 165000
8 2024-01-09 55000 20000 35000 200000
9 2024-01-10 0 18000 -18000 182000
Note

练习: 1. 创建一个12个月的销售数据集 2. 计算每月的环比增长率 3. 计算6个月移动平均 4. 找出销售额低于移动平均的月份

4.9 实用工具函数

自定义函数

定义函数并应用到DataFrame:

# 定义一个函数将余额转换为万元并四舍五入
def to_wan_yuan(x):
    return round(x / 10000, 2)

# 应用到余额列
df['balance'].transform(to_wan_yuan)
1001     5.0
1002    20.0
1122    15.0
1123    18.0
1601    30.0
2202    10.0
4001    68.0
Name: balance, dtype: float64

字符串操作

转换为小写:

# 将账户类型转换为小写
df['account_type'].str.lower()
1001       资产
1002       资产
1122       资产
1123       资产
1601       资产
2202       负债
4001    所有者权益
Name: account_type, dtype: object

获取字符串长度

# 获取账户名称的长度
df['account_name'].str.len()
1001    2
1002    4
1122    4
1123    2
1601    4
2202    4
4001    4
Name: account_name, dtype: int64

判断是否包含特定字符

# 判断账户名称是否包含"账款"
df['account_name'].str.contains('账款')
1001    False
1002    False
1122     True
1123    False
1601    False
2202     True
4001    False
Name: account_name, dtype: bool

字符串索引

# 获取账户名称的第一个字
df['account_name'].str[0]
1001    现
1002    银
1122    应
1123    存
1601    固
2202    应
4001    实
Name: account_name, dtype: object

随机抽样

抽取指定数量的样本:

df.sample(2)
account_name account_type balance balance_direction
1601 固定资产 资产 300000.0
1123 存货 资产 180000.0

按比例抽样

df.sample(frac = 0.4)
account_name account_type balance balance_direction
1001 现金 资产 50000.0
2202 应付账款 负债 100000.0
4001 实收资本 所有者权益 680000.0

4.10 数据合并

concat合并

用标签对齐合并数据(index / columns)

创建示例数据:

df1 = pd.DataFrame(
  {'A': ['A0', 'A1', 'A2', 'A3'],
    'B': ['B0', 'B1', 'B2', 'B3'],
    'C': ['C0', 'C1', 'C2', 'C3'],
    'D': ['D0', 'D1', 'D2', 'D3']})


df2 = pd.DataFrame(
  {'A': ['A4', 'A5', 'A6', 'A7'],
    'B': ['B4', 'B5', 'B6', 'B7'],
    'C': ['C4', 'C5', 'C6', 'C7'],
    'D': ['D4', 'D5', 'D6', 'D7']},
  index=[4, 5, 6, 7])

纵向合并(axis=0):

pd.concat([df1, df2], axis= 0, sort=False)
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7

横向合并(axis=1) - 创建第三个DataFrame:

df3 = pd.DataFrame(
  {'B': ['B2', 'B3', 'B6', 'B7'],
    'D': ['D2', 'D3', 'D6', 'D7'],
    'F': ['F2', 'F3', 'F6', 'F7']},
  index=[2, 3, 6, 7])
df3
B D F
2 B2 D2 F2
3 B3 D3 F3
6 B6 D6 F6
7 B7 D7 F7

横向合并:

pd.concat([df1, df3], axis= 1, sort=False)
A B C D B D F
0 A0 B0 C0 D0 NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3
6 NaN NaN NaN NaN B6 D6 F6
7 NaN NaN NaN NaN B7 D7 F7

merge合并

用values合并数据,参考文档: pandas.merge()

准备数据:

df1['n1'] = df1.A.str[-1]
df3['n3'] = df3.B.str[-1]

执行merge操作:

df1.merge(df3, left_on='n1', right_on='n3', how='inner')
A B_x C D_x n1 B_y D_y F n3
0 A2 B2 C2 D2 2 B2 D2 F2 2
1 A3 B3 C3 D3 3 B3 D3 F3 3

4.11 时间日期处理

创建日期数据:

data = {
    'date': ['2020-08-25', '2021-07-26',
    '2022-06-27', '2023-05-28', '2024-04-29'],
    'value': [10, 15, 7, 25, 30]
}

df = pd.DataFrame(data)
# Convert the 'date' column to datetime format
df['date'] = pd.to_datetime(df['date'])
df.dtypes
date     datetime64[ns]
value             int64
dtype: object

提取年份:

df.date.dt.year
0    2020
1    2021
2    2022
3    2023
4    2024
Name: date, dtype: int32

提取月份:

df.date.dt.month
0    8
1    7
2    6
3    5
4    4
Name: date, dtype: int32

提取周数:

df.date.dt.isocalendar().week
0    35
1    30
2    26
3    21
4    18
Name: week, dtype: UInt32

提取日:

df.date.dt.day
0    25
1    26
2    27
3    28
4    29
Name: date, dtype: int32

星期几:

df.date.dt.day_of_week
# Monday=0, Sunday=6
# df.date.dt.day_name()
0    1
1    0
2    0
3    6
4    0
Name: date, dtype: int32

一年中的第几天:

df.date.dt.day_of_year
0    238
1    207
2    178
3    148
4    120
Name: date, dtype: int32

4.12 数据可视化

启用matplotlib内联显示:

%matplotlib inline

直方图:

from dfply import diamonds
diamonds.price.hist()

散点图:

from dfply import diamonds
diamonds.plot.scatter('carat', 'price')

seaborn散点图:

import seaborn as sns
sns.scatterplot(x="carat", y="price", hue="cut", data=diamonds)

4.13 分组与聚合(Grouping & Aggregation)

学习目标: - 掌握按类别分组汇总数据 - 学会多维度聚合 - 创建会计汇总报表 - 理解透视表在财务分析中的应用

基本分组

创建交易明细数据:

# 创建会计分录数据
ledger_data = pd.DataFrame({
    'date': ['2024-01-05', '2024-01-05', '2024-01-06', '2024-01-06', '2024-01-07', '2024-01-07'],
    'account': ['现金', '应收账款', '现金', '应收账款', '现金', '销售收入'],
    'category': ['资产', '资产', '资产', '资产', '资产', '收入'],
    'debit': [10000, 5000, 8000, 3000, 6000, 0],
    'credit': [0, 0, 0, 0, 0, 6000]
})
ledger_data
date account category debit credit
0 2024-01-05 现金 资产 10000 0
1 2024-01-05 应收账款 资产 5000 0
2 2024-01-06 现金 资产 8000 0
3 2024-01-06 应收账款 资产 3000 0
4 2024-01-07 现金 资产 6000 0
5 2024-01-07 销售收入 收入 0 6000

按账户分组求和:

# 按账户汇总借贷金额
ledger_data.groupby('account').agg({
    'debit': 'sum',
    'credit': 'sum'
}).reset_index()
account debit credit
0 应收账款 8000 0
1 现金 24000 0
2 销售收入 0 6000

按类别分组:

# 按账户类别汇总
ledger_data.groupby('category').agg({
    'debit': 'sum',
    'credit': 'sum'
}).reset_index()
category debit credit
0 收入 0 6000
1 资产 32000 0

多维度聚合

创建更复杂的销售数据:

# 创建销售明细数据
sales_detail = pd.DataFrame({
    'date': pd.to_datetime(['2024-01-15', '2024-01-16', '2024-01-15', '2024-01-16',
                            '2024-02-15', '2024-02-16', '2024-02-15', '2024-02-16']),
    'region': ['华东', '华东', '华南', '华南', '华东', '华东', '华南', '华南'],
    'product': ['A产品', 'B产品', 'A产品', 'B产品', 'A产品', 'B产品', 'A产品', 'B产品'],
    'quantity': [100, 150, 120, 80, 110, 160, 130, 90],
    'unit_price': [100, 150, 100, 150, 100, 150, 100, 150]
})

# 计算销售金额
sales_detail['amount'] = sales_detail['quantity'] * sales_detail['unit_price']
sales_detail
date region product quantity unit_price amount
0 2024-01-15 华东 A产品 100 100 10000
1 2024-01-16 华东 B产品 150 150 22500
2 2024-01-15 华南 A产品 120 100 12000
3 2024-01-16 华南 B产品 80 150 12000
4 2024-02-15 华东 A产品 110 100 11000
5 2024-02-16 华东 B产品 160 150 24000
6 2024-02-15 华南 A产品 130 100 13000
7 2024-02-16 华南 B产品 90 150 13500

多列聚合:

# 按地区和产品分组,计算多个统计量
sales_summary = sales_detail.groupby(['region', 'product']).agg({
    'quantity': 'sum',
    'amount': ['sum', 'mean', 'count']
}).round(2)
sales_summary
quantity amount
sum sum mean count
region product
华东 A产品 210 21000 10500.0 2
B产品 310 46500 23250.0 2
华南 A产品 250 25000 12500.0 2
B产品 170 25500 12750.0 2

自定义聚合函数:

# 按地区汇总,使用自定义聚合
regional_summary = sales_detail.groupby('region').agg({
    'amount': ['sum', 'mean', 'max', 'min'],
    'quantity': 'sum'
})
regional_summary.columns = ['总金额', '平均金额', '最大单笔', '最小单笔', '总数量']
regional_summary
总金额 平均金额 最大单笔 最小单笔 总数量
region
华东 67500 16875.0 24000 10000 520
华南 50500 12625.0 13500 12000 420
Tip

会计应用: 分组聚合常用于: - 按科目汇总账目 - 按部门/项目汇总费用 - 按期间汇总收入成本 - 生成试算平衡表

创建汇总表(带小计和合计)

# 创建费用明细数据
expense_data = pd.DataFrame({
    'department': ['销售部', '销售部', '财务部', '财务部', '人力部', '人力部'],
    'category': ['差旅费', '办公费', '差旅费', '办公费', '差旅费', '办公费'],
    'amount': [5000, 2000, 3000, 1500, 2000, 1000]
})

# 按部门和类别分组
expense_summary = expense_data.groupby(['department', 'category'])['amount'].sum()
print("明细汇总:")
print(expense_summary)
print("\n部门小计:")
print(expense_summary.groupby('department').sum())
print(f"\n总计: ¥{expense_data['amount'].sum():,.2f}")
明细汇总:
department  category
人力部         办公费         1000
            差旅费         2000
财务部         办公费         1500
            差旅费         3000
销售部         办公费         2000
            差旅费         5000
Name: amount, dtype: int64

部门小计:
department
人力部    3000
财务部    4500
销售部    7000
Name: amount, dtype: int64

总计: ¥14,500.00

条件聚合

# 创建应收账款账龄数据
receivables = pd.DataFrame({
    'customer': ['客户A', '客户B', '客户C', '客户D', '客户E'],
    'invoice_date': pd.to_datetime(['2024-01-01', '2024-02-15', '2024-03-10',
                                    '2024-01-20', '2024-02-28']),
    'amount': [50000, 30000, 40000, 25000, 35000],
    'paid': [True, False, False, True, False]
})

# 计算账龄(假设今天是2024-04-01)
current_date = pd.to_datetime('2024-04-01')
receivables['days_outstanding'] = (current_date - receivables['invoice_date']).dt.days

receivables
customer invoice_date amount paid days_outstanding
0 客户A 2024-01-01 50000 True 91
1 客户B 2024-02-15 30000 False 46
2 客户C 2024-03-10 40000 False 22
3 客户D 2024-01-20 25000 True 72
4 客户E 2024-02-28 35000 False 33

按付款状态分组:

# 按付款状态汇总
payment_summary = receivables.groupby('paid').agg({
    'amount': ['sum', 'count'],
    'days_outstanding': 'mean'
})
payment_summary.columns = ['总金额', '笔数', '平均账龄']
payment_summary
总金额 笔数 平均账龄
paid
False 105000 3 33.666667
True 75000 2 81.500000

创建账龄分析:

# 创建账龄区间
receivables['aging_bucket'] = pd.cut(receivables['days_outstanding'],
                                     bins=[0, 30, 60, 90, float('inf')],
                                     labels=['0-30天', '31-60天', '61-90天', '90天以上'])

# 按账龄区间汇总
aging_analysis = receivables.groupby('aging_bucket')['amount'].agg(['sum', 'count'])
aging_analysis.columns = ['金额', '笔数']
aging_analysis
金额 笔数
aging_bucket
0-30天 40000 1
31-60天 65000 2
61-90天 25000 1
90天以上 50000 1
Note

练习: 1. 创建一个包含多个月销售数据的DataFrame 2. 按月份和产品分组,计算销售总额 3. 找出每月销售最高的产品 4. 计算每个产品的月平均销售额

Excel风格的对比

会计师常用的Excel功能在pandas中的对应操作:

Excel功能 Pandas方法 说明
SUMIF groupby().sum() 条件求和
COUNTIF groupby().count() 条件计数
AVERAGEIF groupby().mean() 条件平均
Subtotal groupby() + agg() 分类小计
Pivot Table pivot_table() 数据透视表
Tip

从Excel到Pandas: 如果你熟悉Excel的数据透视表,pandas的pivot_table()groupby()可以实现相同甚至更强大的功能。

数据透视表(Pivot Tables)

学习目标: - 掌握创建数据透视表 - 学会多维度数据分析 - 创建财务交叉报表 - 生成专业的财务分析报告

pivot()基础

创建销售数据:

# 创建简单的销售数据
simple_sales = pd.DataFrame({
    'date': ['2024-01', '2024-01', '2024-02', '2024-02'],
    'region': ['华东', '华南', '华东', '华南'],
    'sales': [200000, 150000, 220000, 180000]
})
simple_sales
date region sales
0 2024-01 华东 200000
1 2024-01 华南 150000
2 2024-02 华东 220000
3 2024-02 华南 180000

创建数据透视表:

# 将数据重塑为表格形式
pivot_df = simple_sales.pivot(
    index='date',
    columns='region',
    values='sales')
pivot_df
region 华东 华南
date
2024-01 200000 150000
2024-02 220000 180000
  • pivot(): 通过将一列的唯一值转换为新列来重塑数据。适用于没有重复索引的情况。
  • pivot_table(): 用于对数据进行分组、汇总和聚合,可以根据数据的不同维度计算聚合值。可以处理重复值。

pivot_table()高级应用

创建更复杂的财务数据:

# 创建包含重复值的月度财务数据
financial_trans = pd.DataFrame({
    'month': ['1月', '1月', '1月', '2月', '2月', '2月', '3月', '3月', '3月'] * 2,
    'category': ['收入', '成本', '费用'] * 6,
    'department': ['销售部']*9 + ['服务部']*9,
    'amount': [500000, 300000, 80000, 520000, 310000, 85000, 480000, 290000, 75000,
               300000, 180000, 50000, 310000, 185000, 52000, 290000, 175000, 48000]
})
financial_trans.head(10)
month category department amount
0 1月 收入 销售部 500000
1 1月 成本 销售部 300000
2 1月 费用 销售部 80000
3 2月 收入 销售部 520000
4 2月 成本 销售部 310000
5 2月 费用 销售部 85000
6 3月 收入 销售部 480000
7 3月 成本 销售部 290000
8 3月 费用 销售部 75000
9 1月 收入 服务部 300000

创建部门-类别透视表:

# 创建透视表,按部门和类别汇总
dept_pivot = pd.pivot_table(financial_trans,
                             values='amount',
                             index='department',
                             columns='category',
                             aggfunc='sum',
                             fill_value=0)
dept_pivot
category 成本 收入 费用
department
服务部 540000 900000 150000
销售部 900000 1500000 240000

添加行列汇总:

# 添加小计和合计
dept_pivot_total = pd.pivot_table(financial_trans,
                                   values='amount',
                                   index='department',
                                   columns='category',
                                   aggfunc='sum',
                                   fill_value=0,
                                   margins=True,  # 添加汇总行和列
                                   margins_name='总计')
dept_pivot_total
category 成本 收入 费用 总计
department
服务部 540000 900000 150000 1590000
销售部 900000 1500000 240000 2640000
总计 1440000 2400000 390000 4230000

计算利润:

# 创建一个包含计算列的透视表副本
profit_analysis = dept_pivot.copy()
profit_analysis['利润'] = profit_analysis['收入'] - profit_analysis['成本'] - profit_analysis['费用']
profit_analysis['利润率%'] = (profit_analysis['利润'] / profit_analysis['收入'] * 100).round(2)
profit_analysis
category 成本 收入 费用 利润 利润率%
department
服务部 540000 900000 150000 210000 23.33
销售部 900000 1500000 240000 360000 24.00
Tip

会计应用: 数据透视表在财务分析中常用于: - 多维度收入分析(按产品、地区、时间) - 成本费用分析(按部门、类别、期间) - 预算vs实际对比 - 盈利能力分析

多级透视表

创建更详细的数据:

# 创建季度-月度-产品销售数据
detailed_sales = pd.DataFrame({
    'quarter': ['Q1', 'Q1', 'Q1', 'Q2', 'Q2', 'Q2'] * 4,
    'month': ['1月', '2月', '3月', '4月', '5月', '6月'] * 4,
    'product': ['产品A']*6 + ['产品B']*6 + ['产品C']*6 + ['产品D']*6,
    'revenue': [100, 110, 105, 120, 115, 125,
                80, 85, 82, 90, 88, 95,
                60, 65, 62, 70, 68, 72,
                50, 55, 52, 60, 58, 62]
})

# 创建多级索引的透视表
multi_pivot = pd.pivot_table(detailed_sales,
                              values='revenue',
                              index=['quarter', 'product'],
                              columns='month',
                              aggfunc='sum',
                              fill_value=0)
multi_pivot
month 1月 2月 3月 4月 5月 6月
quarter product
Q1 产品A 100 110 105 0 0 0
产品B 80 85 82 0 0 0
产品C 60 65 62 0 0 0
产品D 50 55 52 0 0 0
Q2 产品A 0 0 0 120 115 125
产品B 0 0 0 90 88 95
产品C 0 0 0 70 68 72
产品D 0 0 0 60 58 62

交叉表(Cross-tabulation)

交叉表用于统计频次:

# 创建交易批准数据
approval_data = pd.DataFrame({
    'department': ['销售部', '采购部', '销售部', '财务部', '采购部',
                   '销售部', '财务部', '采购部', '销售部', '财务部'],
    'status': ['已批准', '已批准', '待审批', '已批准', '已拒绝',
               '已批准', '待审批', '已批准', '已拒绝', '已批准'],
    'amount': [50000, 30000, 45000, 25000, 20000,
               55000, 28000, 35000, 40000, 22000]
})

# 创建交叉表统计各状态的笔数
status_crosstab = pd.crosstab(approval_data['department'],
                               approval_data['status'],
                               margins=True,
                               margins_name='总计')
status_crosstab
status 已批准 已拒绝 待审批 总计
department
财务部 2 0 1 3
采购部 2 1 0 3
销售部 2 1 1 4
总计 6 2 2 10

交叉表with汇总金额:

# 统计各状态的总金额
amount_crosstab = pd.crosstab(approval_data['department'],
                               approval_data['status'],
                               values=approval_data['amount'],
                               aggfunc='sum',
                               margins=True,
                               margins_name='总计')
amount_crosstab.fillna(0)
status 已批准 已拒绝 待审批 总计
department
财务部 47000.0 0.0 28000.0 75000
采购部 65000.0 20000.0 0.0 85000
销售部 105000.0 40000.0 45000.0 190000
总计 217000.0 60000.0 73000.0 350000
Note

练习: 1. 创建一个包含多个产品线、多个地区、多个月份的销售数据 2. 使用pivot_table创建 地区×产品 的销售金额透视表 3. 添加margins参数显示小计 4. 计算每个地区各产品的销售占比

stack和unstack

stack操作(列转行):

pivot_df.stack()
date     region
2024-01  华东        200000
         华南        150000
2024-02  华东        220000
         华南        180000
dtype: int64

unstack操作(行转列):

pivot_df.stack().unstack()
region 华东 华南
date
2024-01 200000 150000
2024-02 220000 180000
  • unstack(): 将最内层的行索引转换为列。
  • stack(): 将最内层的列索引转换为行索引,创建一个具有多重索引的 Series。

4.14 综合案例: 销售收入分析

案例背景: 某零售公司需要分析2024年第一季度的销售情况,包括不同产品线、不同地区的销售表现,以及应收账款的管理情况。

步骤1: 准备数据

# 创建销售交易数据
sales_transactions = pd.DataFrame({
    'transaction_id': ['T001', 'T002', 'T003', 'T004', 'T005', 'T006', 'T007', 'T008',
                       'T009', 'T010', 'T011', 'T012', 'T013', 'T014', 'T015'],
    'date': pd.to_datetime(['2024-01-05', '2024-01-08', '2024-01-12', '2024-01-15', '2024-01-20',
                            '2024-02-03', '2024-02-07', '2024-02-14', '2024-02-18', '2024-02-25',
                            '2024-03-05', '2024-03-10', '2024-03-15', '2024-03-20', '2024-03-28']),
    'customer': ['客户A', '客户B', '客户A', '客户C', '客户B',
                 '客户A', '客户D', '客户C', '客户B', '客户E',
                 '客户A', '客户D', '客户C', '客户E', '客户B'],
    'product_line': ['电子产品', '家居用品', '电子产品', '服装', '家居用品',
                     '电子产品', '服装', '服装', '电子产品', '家居用品',
                     '电子产品', '家居用品', '服装', '电子产品', '家居用品'],
    'region': ['华东', '华南', '华东', '华北', '华南',
               '华东', '华北', '华北', '华南', '华中',
               '华东', '华北', '华北', '华中', '华南'],
    'quantity': [50, 30, 45, 60, 35, 55, 40, 50, 38, 42, 52, 45, 55, 48, 40],
    'unit_price': [2000, 500, 2000, 300, 500, 2000, 300, 300, 2000, 500, 2000, 500, 300, 2000, 500],
    'payment_status': ['已付', '未付', '已付', '未付', '已付',
                      '已付', '未付', '已付', '未付', '已付',
                      '已付', '未付', '未付', '已付', '未付']
})

# 计算销售金额
sales_transactions['amount'] = sales_transactions['quantity'] * sales_transactions['unit_price']

print(f"共{len(sales_transactions)}笔交易")
sales_transactions.head()
共15笔交易
transaction_id date customer product_line region quantity unit_price payment_status amount
0 T001 2024-01-05 客户A 电子产品 华东 50 2000 已付 100000
1 T002 2024-01-08 客户B 家居用品 华南 30 500 未付 15000
2 T003 2024-01-12 客户A 电子产品 华东 45 2000 已付 90000
3 T004 2024-01-15 客户C 服装 华北 60 300 未付 18000
4 T005 2024-01-20 客户B 家居用品 华南 35 500 已付 17500

步骤2: 数据清洗与验证

# 检查数据质量
print("=== 数据质量检查 ===")
print(f"缺失值检查:")
print(sales_transactions.isnull().sum())

print(f"\n重复交易ID检查:")
duplicate_ids = sales_transactions['transaction_id'].duplicated().sum()
print(f"重复的交易ID数量: {duplicate_ids}")

print(f"\n金额异常值检查:")
print(sales_transactions['amount'].describe())

# 验证数据完整性
assert sales_transactions['transaction_id'].is_unique, "存在重复的交易ID!"
print("\n✓ 数据验证通过")
=== 数据质量检查 ===
缺失值检查:
transaction_id    0
date              0
customer          0
product_line      0
region            0
quantity          0
unit_price        0
payment_status    0
amount            0
dtype: int64

重复交易ID检查:
重复的交易ID数量: 0

金额异常值检查:
count        15.000000
mean      48900.000000
std       40517.720991
min       12000.000000
25%       17000.000000
50%       21000.000000
75%       93000.000000
max      110000.000000
Name: amount, dtype: float64

✓ 数据验证通过

步骤3: 财务分析

3.1 总体销售概况

# 计算总体指标
total_revenue = sales_transactions['amount'].sum()
total_transactions = len(sales_transactions)
average_transaction = sales_transactions['amount'].mean()

print("=== 第一季度销售概况 ===")
print(f"总销售额: ¥{total_revenue:,.2f}")
print(f"交易笔数: {total_transactions}")
print(f"平均交易额: ¥{average_transaction:,.2f}")

# 按付款状态分析
payment_summary = sales_transactions.groupby('payment_status')['amount'].agg(['sum', 'count'])
payment_summary.columns = ['金额', '笔数']
print("\n按付款状态分类:")
print(payment_summary)

收款率 = payment_summary.loc['已付', '金额'] / total_revenue * 100
print(f"\n收款率: {收款率:.2f}%")
=== 第一季度销售概况 ===
总销售额: ¥733,500.00
交易笔数: 15
平均交易额: ¥48,900.00

按付款状态分类:
                    金额  笔数
payment_status            
已付              553500   8
未付              180000   7

收款率: 75.46%

3.2 产品线分析

# 产品线业绩分析
product_analysis = sales_transactions.groupby('product_line').agg({
    'amount': ['sum', 'mean', 'count'],
    'quantity': 'sum'
}).round(2)

product_analysis.columns = ['销售总额', '平均订单额', '订单数', '销售数量']

# 计算各产品线占比
product_analysis['收入占比%'] = (product_analysis['销售总额'] / total_revenue * 100).round(2)

# 按销售额排序
product_analysis = product_analysis.sort_values('销售总额', ascending=False)

print("=== 产品线业绩分析 ===")
print(product_analysis)
=== 产品线业绩分析 ===
                销售总额    平均订单额  订单数  销售数量  收入占比%
product_line                                   
电子产品          576000  96000.0    6   288  78.53
家居用品           96000  19200.0    5   192  13.09
服装             61500  15375.0    4   205   8.38

3.3 地区分析

# 创建地区-产品线透视表
regional_pivot = pd.pivot_table(sales_transactions,
                                 values='amount',
                                 index='region',
                                 columns='product_line',
                                 aggfunc='sum',
                                 fill_value=0,
                                 margins=True,
                                 margins_name='总计')

print("=== 地区×产品线销售矩阵 ===")
print(regional_pivot)

# 找出各地区的主打产品
print("\n各地区销售最高的产品线:")
for region in regional_pivot.index[:-1]:  # 排除总计行
    top_product = regional_pivot.loc[region].drop('总计').idxmax()
    top_amount = regional_pivot.loc[region, top_product]
    print(f"{region}: {top_product}{top_amount:,.2f})")
=== 地区×产品线销售矩阵 ===
product_line   家居用品     服装    电子产品      总计
region                                    
华东                0      0  404000  404000
华中            21000      0   96000  117000
华北            22500  61500       0   84000
华南            52500      0   76000  128500
总计            96000  61500  576000  733500

各地区销售最高的产品线:
华东: 电子产品 (¥404,000.00)
华中: 电子产品 (¥96,000.00)
华北: 服装 (¥61,500.00)
华南: 电子产品 (¥76,000.00)

3.4 时间趋势分析

# 添加月份列
sales_transactions['month'] = sales_transactions['date'].dt.to_period('M')

# 按月统计
monthly_revenue = sales_transactions.groupby('month').agg({
    'amount': 'sum',
    'transaction_id': 'count'
})
monthly_revenue.columns = ['销售额', '交易笔数']

# 计算环比增长率
monthly_revenue['环比增长率%'] = monthly_revenue['销售额'].pct_change() * 100

print("=== 月度销售趋势 ===")
print(monthly_revenue)

# 计算累计收入
monthly_revenue['累计收入'] = monthly_revenue['销售额'].cumsum()
print("\n包含累计收入:")
print(monthly_revenue[['销售额', '累计收入']])
=== 月度销售趋势 ===
            销售额  交易笔数     环比增长率%
month                           
2024-01  240500     5        NaN
2024-02  234000     5  -2.702703
2024-03  259000     5  10.683761

包含累计收入:
            销售额    累计收入
month                  
2024-01  240500  240500
2024-02  234000  474500
2024-03  259000  733500

3.5 客户分析

# 客户价值分析
customer_analysis = sales_transactions.groupby('customer').agg({
    'amount': 'sum',
    'transaction_id': 'count'
})
customer_analysis.columns = ['总购买额', '购买次数']

# 计算平均订单价值
customer_analysis['平均订单额'] = customer_analysis['总购买额'] / customer_analysis['购买次数']

# 按总购买额排序
customer_analysis = customer_analysis.sort_values('总购买额', ascending=False)

# 计算客户贡献度
customer_analysis['收入占比%'] = (customer_analysis['总购买额'] / total_revenue * 100).round(2)

print("=== 客户价值分析 ===")
print(customer_analysis)

# 识别VIP客户(前20%)
vip_threshold = customer_analysis['总购买额'].quantile(0.8)
vip_customers = customer_analysis[customer_analysis['总购买额'] >= vip_threshold]
print(f"\nVIP客户(前20%):")
print(vip_customers)
=== 客户价值分析 ===
            总购买额  购买次数     平均订单额  收入占比%
customer                               
客户A       404000     4  101000.0  55.08
客户B       128500     4   32125.0  17.52
客户E       117000     2   58500.0  15.95
客户C        49500     3   16500.0   6.75
客户D        34500     2   17250.0   4.70

VIP客户(前20%):
            总购买额  购买次数     平均订单额  收入占比%
customer                               
客户A       404000     4  101000.0  55.08

步骤4: 应收账款分析

# 筛选未付款交易
receivables = sales_transactions[sales_transactions['payment_status'] == '未付'].copy()

# 计算账龄(截至2024-04-01)
current_date = pd.to_datetime('2024-04-01')
receivables['days_outstanding'] = (current_date - receivables['date']).dt.days

# 创建账龄区间
receivables['aging_bucket'] = pd.cut(receivables['days_outstanding'],
                                     bins=[0, 30, 60, 90, float('inf')],
                                     labels=['0-30天', '31-60天', '61-90天', '90天以上'])

print("=== 应收账款账龄分析 ===")
print(f"应收账款总额: ¥{receivables['amount'].sum():,.2f}")
print(f"应收笔数: {len(receivables)}")

# 账龄分布
aging_analysis = receivables.groupby('aging_bucket')['amount'].agg(['sum', 'count'])
aging_analysis.columns = ['金额', '笔数']
aging_analysis['占比%'] = (aging_analysis['金额'] / receivables['amount'].sum() * 100).round(2)
print("\n账龄分布:")
print(aging_analysis)

# 按客户统计应收
customer_receivables = receivables.groupby('customer').agg({
    'amount': 'sum',
    'days_outstanding': 'mean'
}).round(2)
customer_receivables.columns = ['应收金额', '平均账龄']
customer_receivables = customer_receivables.sort_values('应收金额', ascending=False)

print("\n按客户应收账款:")
print(customer_receivables)
=== 应收账款账龄分析 ===
应收账款总额: ¥180,000.00
应收笔数: 7

账龄分布:
                 金额  笔数    占比%
aging_bucket                  
0-30天         59000   3  32.78
31-60天        88000   2  48.89
61-90天        33000   2  18.33
90天以上             0   0   0.00

按客户应收账款:
            应收金额   平均账龄
customer               
客户B       111000  43.67
客户C        34500  47.00
客户D        34500  38.00

步骤5: 生成管理报告

print("=" * 60)
print("         2024年第一季度销售分析报告")
print("=" * 60)

print(f"\n一、总体业绩")
print(f"   销售总额: ¥{total_revenue:,.2f}")
print(f"   交易笔数: {total_transactions}")
print(f"   平均订单: ¥{average_transaction:,.2f}")
print(f"   收款率: {收款率:.2f}%")

print(f"\n二、产品线表现(按销售额排序)")
for idx, row in product_analysis.head(3).iterrows():
    print(f"   {idx}: ¥{row['销售总额']:,.2f} ({row['收入占比%']}%)")

print(f"\n三、地区表现(销售总额排序)")
regional_total = regional_pivot.iloc[:-1, -1].sort_values(ascending=False)
for region, amount in regional_total.head(3).items():
    pct = amount / total_revenue * 100
    print(f"   {region}: ¥{amount:,.2f} ({pct:.2f}%)")

print(f"\n四、TOP3客户")
for idx, row in customer_analysis.head(3).iterrows():
    print(f"   {idx}: ¥{row['总购买额']:,.2f} ({row['收入占比%']}%), {row['购买次数']}次购买")

print(f"\n五、应收账款预警")
print(f"   应收总额: ¥{receivables['amount'].sum():,.2f}")
print(f"   超60天应收: ¥{aging_analysis.loc[['61-90天', '90天以上'], '金额'].sum():,.2f}")
over_60_pct = aging_analysis.loc[['61-90天', '90天以上'], '金额'].sum() / receivables['amount'].sum() * 100
print(f"   账龄风险: {over_60_pct:.2f}% 超过60天")

print("\n" + "=" * 60)
============================================================
         2024年第一季度销售分析报告
============================================================

一、总体业绩
   销售总额: ¥733,500.00
   交易笔数: 15
   平均订单: ¥48,900.00
   收款率: 75.46%

二、产品线表现(按销售额排序)
   电子产品: ¥576,000.00 (78.53%)
   家居用品: ¥96,000.00 (13.09%)
   服装: ¥61,500.00 (8.38%)

三、地区表现(销售总额排序)
   华东: ¥404,000.00 (55.08%)
   华南: ¥128,500.00 (17.52%)
   华中: ¥117,000.00 (15.95%)

四、TOP3客户
   客户A: ¥404,000.00 (55.08%), 4.0次购买
   客户B: ¥128,500.00 (17.52%), 4.0次购买
   客户E: ¥117,000.00 (15.95%), 2.0次购买

五、应收账款预警
   应收总额: ¥180,000.00
   超60天应收: ¥33,000.00
   账龄风险: 18.33% 超过60天

============================================================
Note

案例小结:

这个综合案例展示了如何使用pandas进行完整的财务数据分析:

  1. 数据准备: 创建和导入交易数据
  2. 数据清洗: 检查缺失值、重复值、异常值
  3. 多维分析: 产品线、地区、时间、客户等多个维度
  4. 财务计算: 累计、占比、增长率等关键指标
  5. 风险评估: 应收账款账龄分析
  6. 报告生成: 汇总关键发现

实践建议: - 尝试修改数据,观察分析结果的变化 - 添加更多维度的分析(如利润分析、成本分析) - 导出结果到Excel,制作可视化图表 - 结合实际业务场景,思考如何应用这些技术

4.15 总结与下一步

Pandas核心概念回顾

通过本章学习,你已经掌握了:

基础操作: - DataFrame和Series的创建和基本属性 - 数据导入导出(CSV, Excel) - 数据选择和筛选(loc, iloc, 条件筛选)

数据处理: - 数据清洗(缺失值、重复值、类型转换) - 数据验证和质量检查 - 数据变形(pivot, melt, stack/unstack)

财务计算: - 累计求和(cumsum) - 百分比计算(pct_change) - 移动平均(rolling) - 财务比率计算

数据聚合: - 分组统计(groupby) - 多维聚合(pivot_table) - 交叉表分析(crosstab)

与Excel的对比

任务 Excel Pandas 优势
数据处理能力 百万行限制 几乎无限制 Pandas处理大数据更快
重复性任务 需要手动 可编程自动化 Pandas节省时间
数据清洗 需要多步操作 一行代码完成 Pandas更高效
复杂计算 公式复杂 代码清晰 Pandas更易维护
版本控制 困难 代码易于追踪 Pandas更专业

学习建议

  1. 多练习: 用实际财务数据练习,理解更深刻
  2. 查文档: pandas官方文档非常详细,遇到问题先查文档
  3. 循序渐进: 从简单操作开始,逐步掌握复杂功能
  4. 结合业务: 思考如何将这些技术应用到实际会计工作中

下一步学习方向

  • 数据可视化: 使用matplotlib和seaborn创建财务图表
  • 数据库操作: 学习SQL,直接从数据库读取财务数据
  • 自动化报表: 结合Excel库(openpyxl)生成格式化的财务报表
  • 高级分析: 时间序列分析、预测模型等

推荐资源

Important

记住: Pandas是一个强大的工具,但工具只是手段,重要的是理解财务数据背后的业务逻辑和会计原理。技术与专业知识相结合,才能发挥最大价值。