import pandas as pd4 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
创建示例数据
让我们创建一个会计科目表作为示例:
# 创建会计科目数据
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)会计背景说明: - 账户编号: 通常使用数字编码,如1开头表示资产,2开头表示负债等 - 借贷方向: 资产类账户余额在借方,负债和所有者权益账户余额在贷方 - 余额: 表示该科目的期末余额
4.2 DataFrame结构与属性
查看DataFrame类型和结构:
type(df)pandas.core.frame.DataFrame
df
可以删掉 labels 的信息,系统会自己给一个编号。
索引(index)
df.indexIndex([1001, 1002, 1122, 1123, 1601, 2202, 4001], dtype='int64')
列名(columns)
df.columnsIndex(['account_name', 'account_type', 'balance', 'balance_direction'], dtype='object')
值(values)
df.valuesarray([['现金', '资产', 50000.0, '借'],
['银行存款', '资产', 200000.0, '借'],
['应收账款', '资产', 150000.0, '借'],
['存货', '资产', 180000.0, '借'],
['固定资产', '资产', 300000.0, '借'],
['应付账款', '负债', 100000.0, '贷'],
['实收资本', '所有者权益', 680000.0, '贷']], dtype=object)
形状(shape)
df.shape(7, 4)
数据类型(dtypes)
df.dtypesaccount_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']
accounts1001 现金
1002 银行存款
1122 应收账款
1123 存货
1601 固定资产
2202 应付账款
4001 实收资本
Name: account_name, dtype: object
查看列的类型:
type(df.account_name)pandas.core.series.Series
访问特定位置的值:
# 访问账户编号为1002的账户名称
accounts[1002]'银行存款'
说明: DataFrame的每一列都是一个Series对象。Series是pandas的一维数组,类似于Excel中的单独一列。
访问行
使用.loc访问行:
# 访问账户编号为1122的科目信息
account_row = df.loc[1122]
account_rowaccount_name 应收账款
account_type 资产
balance 150000.0
balance_direction 借
Name: 1122, dtype: object
访问行中的特定列:
# 获取该科目的账户类型
account_row['account_type']'资产'
查看行的类型:
type(account_row)pandas.core.series.Series
说明: 访问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,XLSX,SQL,JSON,HTML,HDF5,SAS,STATA,Parquet,SQL等 - 参考文档: https://pandas.pydata.org/pandas-docs/stable/reference/io.html
读取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'])会计应用: 从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)常见错误: 忘记设置 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)
学习目标: - 识别和处理缺失值 - 检测和删除重复数据 - 转换数据类型 - 处理会计数据中的特殊格式
为什么数据清洗很重要? 会计数据常常来自不同系统,可能包含缺失值、重复记录、格式不统一等问题。数据清洗是数据分析的第一步,决定了分析结果的准确性。
检查缺失值
创建示例财务数据(包含缺失值):
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 | 资产 |
会计应用: 对于财务金额,通常用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 | 销售收入 |
常见错误: 删除重复数据前,要确认哪些字段组合应该是唯一的。有时看似重复的记录可能是合法的(如相同客户的多次交易)。
数据类型转换
创建需要类型转换的数据:
# 创建混合类型的财务数据
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.dtypesaccount 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.dtypesaccount object
revenue object
cost object
quantity int64
revenue_clean float64
cost_clean float64
dtype: object
会计应用: - 货币金额常包含货币符号($, ¥)和千位分隔符 - 负数可能用括号表示,如 (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 |
练习: 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 | 贷 |
注意: - 使用 & 表示”且”(AND) - 使用 | 表示”或”(OR) - 每个条件必须用括号括起来 - 不能使用Python的 and 和 or 关键字
使用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 | 借 |
会计应用: 条件筛选常用于: - 筛选特定类别的科目(资产、负债等) - 查找大额交易或余额 - 识别异常账户 - 生成特定范围的财务报表
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 |
会计应用: 累计求和常用于: - 年初至今(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 |
会计应用: 移动平均可以: - 平滑季节性波动 - 识别长期趋势 - 预测未来表现
财务比率计算
创建财务报表数据:
# 创建资产负债表和利润表数据
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 |
练习: 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.dtypesdate datetime64[ns]
value int64
dtype: object
提取年份:
df.date.dt.year0 2020
1 2021
2 2022
3 2023
4 2024
Name: date, dtype: int32
提取月份:
df.date.dt.month0 8
1 7
2 6
3 5
4 4
Name: date, dtype: int32
提取周数:
df.date.dt.isocalendar().week0 35
1 30
2 26
3 21
4 18
Name: week, dtype: UInt32
提取日:
df.date.dt.day0 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_year0 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 |
会计应用: 分组聚合常用于: - 按科目汇总账目 - 按部门/项目汇总费用 - 按期间汇总收入成本 - 生成试算平衡表
创建汇总表(带小计和合计)
# 创建费用明细数据
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 |
练习: 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() | 数据透视表 |
从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 |
会计应用: 数据透视表在财务分析中常用于: - 多维度收入分析(按产品、地区、时间) - 成本费用分析(按部门、类别、期间) - 预算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 |
练习: 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天
============================================================
案例小结:
这个综合案例展示了如何使用pandas进行完整的财务数据分析:
- 数据准备: 创建和导入交易数据
- 数据清洗: 检查缺失值、重复值、异常值
- 多维分析: 产品线、地区、时间、客户等多个维度
- 财务计算: 累计、占比、增长率等关键指标
- 风险评估: 应收账款账龄分析
- 报告生成: 汇总关键发现
实践建议: - 尝试修改数据,观察分析结果的变化 - 添加更多维度的分析(如利润分析、成本分析) - 导出结果到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更专业 |
学习建议
- 多练习: 用实际财务数据练习,理解更深刻
- 查文档: pandas官方文档非常详细,遇到问题先查文档
- 循序渐进: 从简单操作开始,逐步掌握复杂功能
- 结合业务: 思考如何将这些技术应用到实际会计工作中
下一步学习方向
- 数据可视化: 使用matplotlib和seaborn创建财务图表
- 数据库操作: 学习SQL,直接从数据库读取财务数据
- 自动化报表: 结合Excel库(openpyxl)生成格式化的财务报表
- 高级分析: 时间序列分析、预测模型等
推荐资源
记住: Pandas是一个强大的工具,但工具只是手段,重要的是理解财务数据背后的业务逻辑和会计原理。技术与专业知识相结合,才能发挥最大价值。