第1章:Pandas数据处理——REITs量化分析的基石
说实话,我刚开始做REITs量化分析的时候,最头疼的不是策略设计,而是数据清洗。你想想看,从不同数据源拿到的REITs数据,格式五花八门,缺失值、异常值满天飞。那时候我就在想,要是有一套标准化的处理流程该多好。
后来我发现,Pandas就是解决这个问题的利器。它就像一把瑞士军刀,能帮你把乱七八糟的数据整理得井井有条。今天我就带你从零开始,掌握Pandas在REITs分析中的核心用法。
本章核心内容:
- Pandas两大核心数据结构:Series和DataFrame
- 数据清洗与预处理实战技巧
- REITs专属财务指标计算:FFO、AFFO、NAV
1.1 Pandas核心数据结构:Series与DataFrame
Pandas里最常用的两个数据结构,说白了就是Series和DataFrame。Series像一列数据,DataFrame像一张表格。我刚开始学的时候,总把这两个搞混,后来发现一个简单的记忆方法:DataFrame是多个Series拼起来的。
1.1.1 Series:一维数据容器
Series可以理解为一个带标签的数组。每个元素都有索引,你可以通过索引快速定位数据。举个例子,我们拿REITs的每日收盘价来说:
import pandas as pd
# 创建REITs收盘价Series
reit_prices = pd.Series([25.3, 25.8, 26.1, 25.9, 26.4],
index=['2024-01-02', '2024-01-03',
'2024-01-04', '2024-01-05', '2024-01-06'],
name='REIT_Close')
print(reit_prices)
输出结果会显示索引和对应的值。我个人习惯用日期做索引,这样后续做时间序列分析会方便很多。
小技巧:创建Series时指定name参数,后续合并到DataFrame时列名会自动匹配,省去重命名的麻烦。
1.1.2 DataFrame:二维表格数据结构
DataFrame才是我们真正干活的主力。它就像Excel表格,有行有列。我在项目中经常用它来存储多只REITs的历史数据:
# 创建REITs多只股票数据
reit_data = pd.DataFrame({
'REIT_A': [25.3, 25.8, 26.1, 25.9, 26.4],
'REIT_B': [32.1, 31.8, 32.5, 32.2, 31.9],
'REIT_C': [18.5, 18.9, 18.7, 19.1, 19.0]
}, index=['2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05', '2024-01-06'])
print(reit_data.head()) # 查看前5行
你想想看,如果手动处理这些数据,得花多少时间?用Pandas几行代码就搞定了。
1.2 数据清洗与预处理实战
真实世界的数据从来不会这么干净。我记得有一次从某数据源抓取REITs财务数据,结果发现20%的数据都是缺失的。嗯,这时候数据清洗就派上用场了。
1.2.1 缺失值处理
缺失值在Pandas里显示为NaN。常见的处理方法有三种:删除、填充、插值。我个人建议,如果缺失比例低于5%,直接删除;如果高于5%,用均值或前向填充。
# 模拟含缺失值的REITs数据
import numpy as np
reit_with_na = reit_data.copy()
reit_with_na.loc['2024-01-03', 'REIT_A'] = np.nan
reit_with_na.loc['2024-01-05', 'REIT_B'] = np.nan
# 方法1:删除含缺失值的行
clean_data = reit_with_na.dropna()
print("删除后数据:\n", clean_data)
# 方法2:用前一个值填充
filled_data = reit_with_na.fillna(method='ffill')
print("前向填充后:\n", filled_data)
注意:千万不要在金融时间序列数据上使用均值填充!我曾经犯过这个错误,结果导致回测结果严重失真。REITs价格有趋势性,用均值填充会破坏这种趋势。
1.2.2 异常值检测
异常值在REITs数据中很常见,比如突然的股价异动。我一般用Z-score方法检测:
from scipy import stats
# 计算Z-score
z_scores = np.abs(stats.zscore(reit_data['REIT_A']))
outliers = reit_data[z_scores > 3]
print(f"检测到{len(outliers)}个异常值")
为什么用3倍标准差?这是统计学上的经验值。当然,你也可以根据实际情况调整阈值。
1.3 REITs财务指标计算
终于到了最核心的部分。FFO、AFFO、NAV这三个指标,是REITs分析的灵魂。不懂这三个指标,你都不好意思说自己懂REITs。
1.3.1 FFO(营运现金流)
FFO全称是Funds From Operations,说白了就是REITs真正赚到的钱。为什么不用净利润?因为REITs的折旧摊销太大了,净利润根本反映不了真实经营情况。
# 计算FFO
def calculate_ffo(net_income, depreciation, amortization,
gains_on_sale):
"""
计算REITs的FFO
FFO = 净利润 + 折旧与摊销 - 资产出售收益
"""
ffo = net_income + depreciation + amortization - gains_on_sale
return ffo
# 示例数据
reit_financials = {
'net_income': 1000000, # 净利润
'depreciation': 200000, # 折旧
'amortization': 50000, # 摊销
'gains_on_sale': 100000 # 资产出售收益
}
ffo = calculate_ffo(**reit_financials)
print(f"FFO = ${ffo:,.2f}")
关键点:FFO是REITs分红的基石。我见过很多新手直接用净利润来估值,结果低估了REITs的真实价值。记住,REITs看FFO,不看净利润。
1.3.2 AFFO(调整后营运现金流)
AFFO是FFO的升级版。它进一步剔除了维持性资本支出。为什么叫「调整后」?因为FFO里还包含了一些非现金项目,AFFO把这些都剔除了,更接近真实的可分配现金流。
def calculate_affo(ffo, maintenance_capex, straight_line_rent,
stock_compensation):
"""
计算AFFO
AFFO = FFO - 维持性资本支出 - 直线法租金调整 - 股权激励
"""
affo = ffo - maintenance_capex - straight_line_rent - stock_compensation
return affo
# 接上面的FFO数据
maintenance_capex = 80000 # 维持性资本支出
straight_line_rent = 20000 # 直线法租金调整
stock_compensation = 15000 # 股权激励
affo = calculate_affo(ffo, maintenance_capex,
straight_line_rent, stock_compensation)
print(f"AFFO = ${affo:,.2f}")
我个人习惯用AFFO来估算REITs的合理分红水平。如果AFFO持续高于分红,说明分红有保障;反之就要警惕了。
1.3.3 NAV(净资产价值)
NAV就是REITs手里资产的市场价值减去负债。说白了,如果今天把REITs清盘了,每股能分到多少钱。
def calculate_nav(property_value, cash, other_assets,
total_liabilities, shares_outstanding):
"""
计算每股NAV
NAV = (资产市场价值 + 现金 + 其他资产 - 总负债) / 总股本
"""
nav_per_share = (property_value + cash + other_assets
- total_liabilities) / shares_outstanding
return nav_per_share
# 示例数据
nav_inputs = {
'property_value': 50000000, # 物业市场价值
'cash': 2000000, # 现金
'other_assets': 1000000, # 其他资产
'total_liabilities': 30000000, # 总负债
'shares_outstanding': 5000000 # 总股本
}
nav = calculate_nav(**nav_inputs)
print(f"每股NAV = ${nav:.2f}")
实战经验:当REITs的市场价格低于NAV时,可能存在投资机会。但要注意,NAV的估算依赖于物业估值,而估值本身就有主观性。我一般会参考多家机构的估值取中位数。
1.4 综合实战:构建REITs财务分析流水线
好了,现在我们把所有知识串起来。下面是一个完整的REITs财务分析流水线:
class REITAnalyzer:
"""REITs财务分析器"""
def __init__(self, financial_data):
self.data = financial_data
self.ffo = None
self.affo = None
self.nav = None
def clean_data(self):
"""数据清洗"""
self.data = self.data.dropna()
# 去除异常值
z_scores = np.abs(stats.zscore(self.data.select_dtypes(
include=[np.number])))
self.data = self.data[(z_scores < 3).all(axis=1)]
return self
def calculate_all_metrics(self):
"""计算所有财务指标"""
self.ffo = calculate_ffo(
self.data['net_income'],
self.data['depreciation'],
self.data['amortization'],
self.data['gains_on_sale']
)
self.affo = calculate_affo(
self.ffo,
self.data['maintenance_capex'],
self.data['straight_line_rent'],
self.data['stock_compensation']
)
self.nav = calculate_nav(
self.data['property_value'],
self.data['cash'],
self.data['other_assets'],
self.data['total_liabilities'],
self.data['shares_outstanding']
)
return {
'FFO': self.ffo,
'AFFO': self.affo,
'NAV': self.nav
}
# 使用示例
sample_data = pd.DataFrame({
'net_income': [1000000],
'depreciation': [200000],
'amortization': [50000],
'gains_on_sale': [100000],
'maintenance_capex': [80000],
'straight_line_rent': [20000],
'stock_compensation': [15000],
'property_value': [50000000],
'cash': [2000000],
'other_assets': [1000000],
'total_liabilities': [30000000],
'shares_outstanding': [5000000]
})
analyzer = REITAnalyzer(sample_data)
results = analyzer.clean_data().calculate_all_metrics()
print("分析结果:")
for key, value in results.items():
print(f"{key}: ${value:,.2f}")
这个类封装了我们今天学的所有内容。你想想看,以后分析新的REITs,只需要传入数据,调用两个方法就能得到核心指标,是不是很方便?
本章总结:
- Series和DataFrame是Pandas的两大核心,DataFrame是分析主力
- 数据清洗要谨慎,金融数据尤其注意不要破坏时间序列特性
- FFO、AFFO、NAV是REITs分析的三大支柱,必须熟练掌握
- 封装成类可以提高代码复用性,建议在实际项目中采用
说实话,这些内容我反复讲过很多次,但每次都有新的体会。REITs量化分析这条路,数据是基础,指标是工具,策略才是灵魂。今天我们把基础打牢了,后面才能走得更远。