3. 数据清洗与预处理:艺术品拍卖数据的获取与标准化
做量化投资的人都知道一句话:垃圾进,垃圾出。艺术品数据更是如此——它比股票数据脏得多。我刚开始做艺术品指数时,天真地以为从数据库拉下来就能直接用,结果模型跑出来一堆负收益的毕加索……嗯,后来我花了整整两周做数据清洗。
今天我们就聊聊,怎么把那些乱七八糟的拍卖记录,变成能喂给量化模型的干净数据。
3.1 数据获取渠道:你的原材料从哪里来?
艺术品拍卖数据不像股票数据那样有统一的交易所接口。目前主流渠道就三个,我一个个说。
3.1.1 Artprice 与 Artnet:两大商业数据库
这两个是行业标配。Artprice 覆盖了全球 7000 多家拍卖行,数据量最大。Artnet 呢,更偏重高端市场,苏富比、佳士得的数据更全。
我个人习惯是:两个都买,交叉验证。为什么?因为同一个拍品在两个平台上的记录可能不一样——比如成交价是否含佣金、货币单位是否统一,这些细节坑很多。
关键区别:
- Artprice:数据量大,但清洗成本高。适合做宽基指数。
- Artnet:数据质量高,但覆盖窄。适合做精品指数。
3.1.2 苏富比/佳士得 API:一手数据源
这两家拍卖行都有自己的 API 接口。我建议优先用它们。为什么?因为数据最原始,没有经过第三方加工。我在项目中遇到过一件事:Artprice 上某件赵无极作品的成交价,比苏富比官方数据高了 12%。后来查出来,是 Artprice 把买家佣金也算进去了,但没标注。
所以我的原则是:能用官方 API 就别用第三方。虽然 API 调用有频率限制,但数据可信度高。
小技巧:苏富比 API 返回的 JSON 里,有个字段叫 priceIncludesPremium,一定要检查这个布尔值。很多坑都出在这里。
3.2 数据清洗流程:把脏数据洗干净
数据拿到手,别急着建模。先过三关:去重、缺失值、异常值。这三步走完,数据才能用。
3.2.1 去重:同一个拍品,别算两次
艺术品市场有个特点:同一件作品可能在不同拍卖行、不同时间反复上拍。比如某件张大千的画,2015 年在嘉德拍过一次,2018 年又在保利拍了一次。这两条记录都要保留——因为它们代表不同的交易。
但真正的重复是什么?是同一次拍卖,被多个渠道收录。比如 Artprice 和 Artnet 都收录了同一场苏富比拍卖的同一件拍品。这时候就要去重。
我的去重逻辑是这样的:
# 伪代码:艺术品去重逻辑
def dedup_artworks(df):
# 用 (艺术家名, 作品名, 拍卖日期, 拍卖行) 四元组做唯一标识
df['dedup_key'] = df['artist'] + '_' + df['title'] + '_' + df['auction_date'] + '_' + df['auction_house']
df = df.drop_duplicates(subset='dedup_key', keep='first')
return df
注意:作品名要标准化。比如“无题”和“Untitled”其实是同一件作品。我建议把所有作品名转成小写,去掉标点符号再比较。
避坑指南:我曾经因为没做作品名标准化,导致某件赵无极的《无题》在数据里出现了 3 条记录——英文名、法文名、中文名各一条。去重后才发现,这 3 条其实是同一场拍卖的同一件作品。
3.2.2 缺失值处理:别随便填 0
艺术品数据缺失很常见。比如尺寸信息、创作年份、甚至成交价都可能缺失。怎么处理?
我的经验是分情况:
- 成交价缺失:直接删除这条记录。因为价格是指数计算的核心,没法补。
- 尺寸缺失:可以用同艺术家、同年代作品的均值填充。但要注意,雕塑和绘画的尺寸差异很大,别混在一起算。
- 创作年份缺失:这个比较棘手。我建议用拍卖年份减去艺术家创作活跃期的中位数来估算。但误差可能很大,要谨慎。
我的原则:缺失率超过 30% 的字段,直接放弃。别为了补数据而引入噪声。
3.2.3 异常值检测:那些离谱的价格
艺术品价格分布是典型的幂律分布——少数天价作品拉高了整体均值。但有些异常值是真的错误,比如把 100 万录成了 100 亿。
我常用的方法是 IQR(四分位距)法:
# 异常值检测:IQR 方法
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 3 * IQR
upper_bound = Q3 + 3 * IQR
# 标记异常值
df['is_outlier'] = (df['price'] < lower_bound) | (df['price'] > upper_bound)
注意:这里我用的是 3 倍 IQR,而不是常见的 1.5 倍。因为艺术品价格波动大,1.5 倍会误杀太多正常记录。比如某件梵高作品拍出 5 亿,用 1.5 倍 IQR 肯定被标记为异常,但它其实是真实交易。
个人经验:异常值不要直接删除。先人工核查。我遇到过一件有趣的事:某件齐白石的作品在数据里显示成交价 1 元,我以为是错误,结果查了原始拍卖记录——确实是 1 元,因为那是慈善拍卖。
3.3 标准化处理:让数据可比较
数据洗干净了,但还不能直接用。因为不同年份、不同国家的价格没有可比性。你需要做两件事:通胀调整和货币转换。
3.3.1 价格通胀调整:把 1980 年的 100 万换算成今天的钱
1980 年花 100 万买一幅画,和今天花 100 万买,完全是两回事。所以要把历史价格调整到今天的购买力。
我用的公式很简单:
# 通胀调整公式
adjusted_price = original_price * (CPI_today / CPI_year_of_sale)
CPI 数据可以从世界银行或各国统计局获取。我建议用美国 CPI作为基准,因为艺术品市场以美元计价为主。
注意:通胀调整只适用于同一货币。如果你有英镑计价的交易,先转成美元,再做通胀调整。顺序不能乱。
3.3.2 货币转换:统一到美元
艺术品市场是全球性的,但计价货币五花八门。英镑、欧元、港币、人民币……必须统一到一个基准货币。我选美元,原因很简单:流动性最好,数据最全。
货币转换的坑在哪里?汇率时点。比如一件作品 2019 年 5 月在伦敦以英镑成交,你应该用 2019 年 5 月的平均汇率,而不是今天的汇率。
# 货币转换示例:英镑转美元
def convert_currency(price_gbp, date):
# 获取该日期的 GBP/USD 汇率
exchange_rate = get_historical_rate('GBP', 'USD', date)
return price_gbp * exchange_rate
避坑指南:我曾经用错了汇率——用 2023 年的汇率去转换 2005 年的英镑交易。结果某件培根的作品价格凭空涨了 40%。后来查出来,是因为英镑在 2005 年比现在值钱得多。
3.4 知识体系总览
说了这么多,我画了一张图帮你理清思路。数据清洗与预处理的核心流程,其实就是从原始数据到标准化数据的三个步骤:获取、清洗、标准化。
这张图把整个流程串起来了。你从左边三个渠道拿到原始数据,经过中间的三步清洗,再到右边的两步标准化,最后得到可以用于指数计算的数据集。每一步都有坑,但走通了,你的指数就有了坚实的基础。
最后说一句:数据清洗这件事,看起来枯燥,但它是整个量化投资的地基。我见过太多人花 90% 的时间调模型,却只花 10% 的时间处理数据。结果模型跑出来,自己都不敢信。记住:数据干净了,模型自然就对了。
公众号:蓝海资料掘金营,微信deep3321