21  数据分析是什么?

21.1 简介

在数据科学中,探索性数据分析 (EDA) 是一种用于分析数据集以总结其主要特征的技术,通常使用可视化方法。EDA 是数据分析过程中的一个重要步骤,因为它可以帮助我们了解数据的结构、分布和潜在的关系。

EDA 通常在数据建模之前进行,以便为后续的建模和分析提供基础。它可以帮助我们识别数据中的模式、趋势和异常值,从而为后续的分析提供指导。 EDA 的主要目标是:

  • 理解数据的分布和结构
  • 识别数据中的模式和趋势
  • 识别数据中的异常值
  • 识别数据中的缺失值
  • 识别数据中的相关性
  • 识别数据中的潜在问题

EDA 通常包括以下步骤:

  1. 数据预处理:
    • 数据清洗:处理缺失值、异常值和重复数据
    • 数据转换:将数据转换为适合分析的格式
    • 数据标准化:将数据标准化到相同的尺度
    • 数据分割:将数据分为训练集和测试集
  2. 数据可视化:
    • 使用统计图表(如直方图、箱线图、散点图等)查看数据分布
    • 使用热图和相关矩阵可视化数据之间的关系
    • 使用时间序列图查看数据随时间的变化
    • 使用地理图查看数据的地理分布
    • 使用网络图查看数据之间的关系
  3. 数据建模:
    • 使用统计模型(如线性回归、逻辑回归等)分析数据
    • 使用机器学习模型(如决策树、随机森林、支持向量机等)分析数据
    • 使用深度学习模型(如神经网络等)分析数据
    • 使用集成学习模型(如随机森林、XGBoost等)分析数据
  4. 数据评估:
    • 使用交叉验证评估模型的性能
    • 使用混淆矩阵评估分类模型的性能
    • 使用均方误差、平均绝对误差等评估回归模型的性能
    • 使用 ROC 曲线、AUC 等评估模型的性能
    • 使用 F1 分数、精确率、召回率等评估模型的性能

21.2 数据长啥样?

在进行统计建模之前,我们通常要先对数据有一个全面的了解,这一步就叫做 探索性数据分析 (exploratory data analysis, EDA)。EDA 的目的不是做推断,而是通过图形和汇总统计等手段,对数据的结构、变量的分布特征、变量之间的关系,以及观测值之间的聚集情况进行初步探索。

有些时候,我们并不清楚数据中有哪些信息,也还没想好要问什么问题,这时 EDA 可以帮助我们发现线索,启发思路。即便研究目标一开始就很明确,EDA 也仍然是不可跳过的环节。我们需要确认:

  • 数据中是否存在缺失值或异常值;
  • 各变量的分布是否合理,是否符合模型假设;
  • 变量之间是否存在相关性,其关系是否与理论一致。

只有对数据的“样貌”做到心中有数,后续的建模和推断工作才可能稳妥有效。

21.2.1 变量类型

在数据分析中,数值变量大致可以分为两类:分类变量数值变量

  • 分类变量 (categorical variables):表示事物的类别属性,常见的如性别、地区、行业等。

    • 名义型变量 (nominal):类别之间没有顺序,例如血型、国籍;
    • 有序型变量 (ordinal):类别之间存在等级或顺序关系,例如学历层次 (小学 < 初中 < 高中 < 本科)。
  • 数值变量 (numeric variables):表示数量大小,既包括取整数的变量,也包括可以取连续小数的变量。

    • 如果某些整数变量的取值非常有限,例如只有 1 到 5 分,我们也可以把它看作分类变量来处理。

准确判断变量类型,有助于我们选择恰当的图形展示方法与统计工具,是数据分析中的基本功之一。

21.3 数据长啥样?从结构化数据说起

我们日常接触到的数据,大多以表格形式存储,每一行是一个观测对象,每一列是一个变量。这类数据称为 结构化数据 (structured data),是数据分析中最常见的形式。

在结构化数据中,每一个变量(字段)通常表示一种特征或属性。根据变量的表现形式与分析方法,变量大致可以分为两类:分类变量数值变量。除此之外,我们在实际项目中还会遇到 非结构化数据半结构化数据,处理方式与建模策略也会有所不同,下面分别介绍。

21.3.1 分类变量与数值变量

21.3.1.1 分类变量 (categorical variables)

分类变量表示对象所属的类别或属性,不直接反映数量大小。根据是否有顺序,可以进一步区分为两种:

  • 名义型变量 (nominal):类别之间没有顺序关系,如性别 (男 / 女)、城市名称、省份等。
  • 有序型变量 (ordinal):类别之间存在明确的等级顺序,如学历 (小学 < 初中 < 高中 < 本科 < 研究生)、满意度评分 (不满意 < 一般 < 满意 < 非常满意)。
import pandas as pd

df = pd.DataFrame({
    "性别": ["男", "女", "女", "男"],
    "学历": ["本科", "硕士", "博士", "本科"]
})
print(df)
  性别  学历
0  男  本科
1  女  硕士
2  女  博士
3  男  本科

继续介绍「数字-文字对应表」,或字典的概念。 目的:便于存储,便于进行数值分析和分类操作

21.3.2 数字-文字对应表的作用

在数据分析中,使用数字-文字对应表(字典)可以将分类变量转换为数值变量,从而便于进行数值分析和分类操作。例如,将学历从“小学”、“初中”等文字形式映射为数字形式(如 1, 2, 3 等),可以方便地进行排序、统计和回归分析等操作。

这种映射方式的优点包括:

  • 便于计算:数值变量可以直接用于数学运算和模型训练。
  • 节省存储空间:数字编码通常比文字占用更少的存储空间。
  • 提高效率:在大规模数据处理中,数值编码的处理速度通常比文字更快。

以下代码展示了如何通过字典将学历字段映射为对应的数值编码。

# 定义学历映射字典
education_mapping = {
    "小学": 1,
    "初中": 2,
    "高中": 3,
    "本科": 4,
    "硕士": 5,
    "博士": 6
}

# 将学历字段映射为数值编码
df["学历编码"] = df["学历"].map(education_mapping)
print(df)
  性别  学历  学历编码
0  男  本科     4
1  女  硕士     5
2  女  博士     6
3  男  本科     4

21.3.2.1 数值变量 (numeric variables)

数值变量表示数量大小,可以进行加减乘除等运算,通常又分为:

  • 离散型变量:只能取有限个整数值,如子女数、评分等级、借款次数等。
  • 连续型变量:可以取任意实数值,如收入、温度、身高、收益率等。

在下面的数据中,虽然三个变量都是数值型变量,但 月薪 通常被视为连续变量,满意度评分 则被视为离散型变量。至于 年龄,则需要根据具体情况来判断:如果年龄只取整数值且在样本中只有不多的几个取值,通常也被视为离散型变量;如果年龄可以取小数值或取值范围很大,则可以视为连续型变量。

此外,满意度评分 事实上具有两层含义:

  • 一方面,它可以单纯地作为分类依据,把顾客分成几个不同的人群;
  • 另一方面,从数值上来讲,3 分确实比 2 分高,4 分比 3 分高,因此它也可以作为数值变量来处理。
df = pd.DataFrame({
    "年龄": [25, 32, 28, 40],
    "月薪": [8000, 12000, 10000, 15000],
    "满意度评分": [3, 4, 5, 2]
})
print(df)
   年龄     月薪  满意度评分
0  25   8000      3
1  32  12000      4
2  28  10000      5
3  40  15000      2

21.3.3 半结构化数据:机器可读的接口与嵌入式格式

半结构化数据 (semi-structured data) 介于结构化与非结构化之间。它没有固定表格格式,但可以通过规则解析提取信息,常见格式包括 JSON、XML、网页嵌入块等。

在实际项目中,金融与政府数据平台往往提供 API 接口或 JSON 格式数据。例如,某公司年报摘要的接口可能返回如下内容:

{
  "company_name": "贵州茅台",
  "stock_code": "600519",
  "report_year": 2023,
  "financials": {
    "revenue": 1360.5,
    "net_profit": 620.3
  },
  "industries": ["食品饮料", "白酒"],
  "announcement_date": "2024-03-25"
}

这类数据可以方便地转化为结构化表格,常用 Python 代码如下:

import json
import pandas as pd

data = '''
{
  "company_name": "贵州茅台",
  "stock_code": "600519",
  "report_year": 2023,
  "financials": {
    "revenue": 1360.5,
    "net_profit": 620.3
  }
}
'''
info = json.loads(data)  # 解析JSON数据

# 打印解析后的数据
print("公司名称:", info["company_name"])
print("净利润:", info["financials"]["net_profit"], "亿元")

# 转换为数据框
df_info = pd.DataFrame([flat_info])
print('-'*50)
print(df_info)
公司名称: 贵州茅台
净利润: 620.3 亿元
--------------------------------------------------
   公司名称    股票代码  报告年度  营业收入(亿元)  净利润(亿元)
0  贵州茅台  600519  2023    1360.5    620.3

这种数据结构非常适合用于系统对接、爬虫采集和接口开发,在财经信息系统中使用极为广泛。

还有些数据虽然表面上存储的很整齐,但也不是结构化数据。例如,网页中的嵌入式格式(如 HTML、Markdown 等)也可以看作半结构化数据。

21.3.4 非结构化数据与整洁数据

非结构化数据 (unstructured data) 是指没有固定字段或列名的数据,最常见的是文本、图像、音频等。其中,金融研究中常见的非结构化数据包括公司公告、新闻、研报等自然语言文本。例如:

“本公司于 2023 年 6 月 1 日,与建行深圳分行签署贷款协议,贷款金额为 2 亿元,期限 3 年,利率为年化 4.2%。本次贷款以公司部分机器设备作抵押,由控股股东提供担保。若未能按期偿还,将触发违约条款。”

“经查,深圳市汇通科技股份有限公司在 2022 年年度报告中存在虚假记载。公司未如实披露其与下属子公司之间的关联交易情况,涉案金额累计达 1.38 亿元,相关资金部分已通过非正常渠道流出。上述行为违反了《证券法》第六十三条第一款的规定。根据《证券法》第二百二十三条的规定,我会决定:对深圳市汇通科技股份有限公司责令改正,给予警告,并处以 600 万元罚款;对时任董事长兼总经理李某某给予警告,处以 120 万元罚款,并采取 5 年市场禁入措施。有关当事人如对本处罚决定不服,可自收到本决定书之日起 60 日内向国务院申请行政复议,或自收到本决定书之日起 6 个月内依法向人民法院提起诉讼。”

今年发展主要预期目标是国内生产总值增长 5% 左右,城镇新增就业 1100 万人以上,居民消费价格涨幅在 3%-5% 之间,单位国内生产总值能耗持续下降。要围绕高质量发展这一首要任务,着力推动先进制造业、数字经济、生物医药等战略性新兴产业集群发展,提升现代化产业体系的韧性与安全性。

鼓励地方因地制宜发展新质生产力,继续实施新能源汽车下乡、智能家电换代等行动,推动重点领域设备更新和消费品以旧换新。坚持绿水青山就是金山银山,强化重点行业污染治理,推进钢铁、电解铝、水泥等行业节能降碳改造,加快构建以新能源为主体的新型电力系统。稳步推进碳达峰碳中和各项工作,推动形成绿色低碳的生产方式和生活方式。

21.3.4.1 整洁数据

在数据清晰阶段,我们要把「非结构化数据 (脏数据)」处理成「结构化数据 (整洁数据)」。

整洁数据 有三条核心原则 (Source: R for Data Science, Chap5):

  • 列独立: 每个变量对应一列,每一列只存储一个变量的信息。
  • 行独立: 每个观测对应一行,每一行只表示一个观测对象。
  • 原子性: 每个单元格只存储一个值,不能有多个信息混杂在同一个格子里。

此外,整洁数据还应遵循以下结构规范:

  • 命名规范性:变量名称应简洁明了,遵循统一规则。多词变量建议使用下划线(如 loan_amount)或驼峰式命名(如 LoanAmount),避免使用过短或含义不明的变量名。
  • 信息完整性:每个变量应配有清晰的标签说明,明确其含义、单位、取值范围等,确保使用者理解一致。
  • 缺失值统一性:全表缺失值标记应统一,如统一使用 .NANULL 表示缺失,避免混用。
  • 量纲一致性:同类变量应统一量纲与单位,如统一使用人民币(元)表示金额,统一使用“米”表示长度,避免混杂导致计算错误。

21.3.5 案例:世纪兴达公司的贷款公告

假设世纪兴达公司 (股票代码:500288) 发布了一则贷款公告:

“本公司于 2023 年 6 月 1 日,与建行深圳分行签署贷款协议,贷款金额为 2 亿元,期限 3 年,利率为年化 4.2%。本次贷款以公司部分机器设备作抵押,由控股股东提供担保。若未能按期偿还,将触发违约条款。”

21.3.5.1 清洁数据处理思路

这段公告以文本段落呈现,信息分散,难以直接用于数据分析,属于典型的非结构化脏数据。我们需要将其中的关键信息提取并整理成结构化数据。

  1. 明确目标字段:目标字段包括贷款银行、贷款金额、贷款时间、利率、期限、是否有抵押、抵押品类型与描述、是否有担保、担保情况、违约条款等。

  2. 信息拆解与归类

    • 贷款银行:建行深圳分行
    • 贷款金额:200000000(元)
    • 贷款时间:2023-06-01
    • 利率:4.20%(年化)
    • 期限:3 年
    • 是否有抵押品:是
    • 抵押品描述:公司部分机器设备
    • 是否有担保:是
    • 担保情况:控股股东提供担保
    • 违约条款:若未能按期偿还,将触发违约条款
  3. 格式标准化

    • 金额统一为阿拉伯数字(单位为元)
    • 日期统一为“YYYY-MM-DD”格式
    • 利率保留两位小数,写作百分比
  4. 缺失值处理:如公告未提及抵押品价值或担保方式,可设为“未披露”或留空,后续视业务需求再补全。

21.3.5.2 半整洁数据

一位同学经过处理后,提取了如下信息:

字段
银行名称 建行深圳分行
贷款金额 2 亿元
签署时间 2023 年 6 月 1 日
贷款期限 3 年
利率 4.2%
是否有抵押品
是否有担保
违约条件 未能按期偿还

21.3.5.3 整洁数据

虽然处理后的数据已经比较规整了,但还没有达到「整洁数据」的要求。进一步处理后,我们可以得到如下表格:

字段
公司名称 世纪兴达
股票代码 500288
银行名称 中国建设银行深圳分行
贷款金额 2000000000
货币单位 人民币
签署时间 2023-06-01
贷款期限(年) 3
利率 0.042
是否有抵押品
抵押品类型 固定资产
抵押品名称 部分机器设备
是否有担保
担保人 控股股东
担保人类型 自然人/法人
违约条件 未能按期偿还

相比之下,更新后的表格做了如下修改:

  • 增加了“公司名称”和“股票代码”字段,便于后续数据关联;
  • 将“银行名称”字段从“建行深圳分行”更改为“中国建设银行深圳分行”,便于后续数据关联;
  • 将“贷款金额”字段转化为数字格式,并增加了“货币单位”字段;
  • 将“利率”字段转化为小数格式;
  • 增加了“抵押品类型”和“抵押品名称”字段,便于后续进行分类;
  • 增加了“担保人”和“担保人类型”字段,便于后续进行分类;

当然,如果数据量比较大,可以删除“是否有抵押品”和“是否有担保”等字段,因为后续数据处理时可以通过“抵押品类型”和“担保人类型”来判断是否有抵押品和担保。另外,表中的布尔值(是/否)也可以用 0 和 1 来表示,一遍节省存储空间。

21.3.5.4 大批量数据的处理

本例仅包含了一家公司的公告,实际分析中我们可能会遇到数万条公告。通常需要按如下流程处理:

  • 人工分析,找出规律。 我们需要随机挑选 50-100 条记录,进行人工分析。主要目的是总结字段特征,并进行统一命名。此过程可能需要反复修正多轮才能形成最终版本。
  • 程序化处理。了解数据的基本特征和分布情况后,也可以通过正则表达式、分词处理等方法,自动化地提取出“公司名称”、“银行名称”、“贷款金额” 等字段,也可以采用大模型进行自动提取。
  • 人工审核。程序化处理后,仍然需要人工审核,确保数据的准确性和完整性。可以随机抽取 1%-5% 的数据进行人工审核。
  • 统计分析。可以借助均值、标准差、分位数等统计指标,以及直方图、箱型图、密度函数图等工具进行可视化,以便确认是否存在异常值。如有异常,可以返回前述步骤修正。

这类任务称为 信息抽取 (information extraction),通常会使用自然语言处理 (NLP) 工具包(如 NLTK、spaCy、transformers 等)来实现。

21.3.5.5 Python 实操

以下是一个简单的示例,使用正则表达式从文本中提取贷款金额:

import pandas as pd
import re

# Variables from the notebook
text = '贷款金额为 2 亿元人民币,期限 3 年,利率为年化 4.2%。'

# 从文本中提取信息并创建数据框

# 提取贷款金额
if amount:
    loan_amount = int(float(amount.group(1)) * 1e8)  # 转换为元
else:
    loan_amount = None

# 提取货币单位
currency = "人民币"  # 假设货币单位为人民币

# 提取签署时间
sign_date = "2023-06-01"  # 假设签署时间固定

# 提取贷款期限
if term:
    loan_term = int(term.group(1))
else:
    loan_term = None

# 提取利率
if rate:
    interest_rate = float(rate.group(1)) / 100  # 转换为小数
else:
    interest_rate = None

# 创建数据框
data = {
    "字段": ["贷款金额", "货币单位", "签署时间", "贷款期限(年)", "利率"],
    "值": [loan_amount, currency, sign_date, loan_term, interest_rate]
}
df_output = pd.DataFrame(data)

# 打印数据框
print(df_output)
        字段           值
0     贷款金额   200000000
1     货币单位         人民币
2     签署时间  2023-06-01
3  贷款期限(年)           3
4       利率       0.042

21.3.6 小结

不同数据类型的核心特征与处理策略如下:

数据类型 示例 特点与处理方式
结构化数据 Excel、CSV、DataFrame 表格 每列为变量,可直接分析建模
非结构化数据 公告文本、PDF、图像、音频 需先用 NLP 或图像处理方法进行结构化
半结构化数据 JSON、XML、网页嵌入内容 用规则或工具解析后可转化为结构化形式

理解不同数据结构,是进入实际分析工作前的重要准备。后续章节中我们将围绕结构化数据展开建模分析,同时逐步引入处理半结构化与非结构化数据的方法。

21.4 后续内容:数据清洗

21.4.1 缺失值处理

缺失值是数据分析中常见的问题,处理缺失值的方法有很多,常用的方法包括: - 删除缺失值:直接删除包含缺失值的行或列 - 填充缺失值:使用均值、中位数、众数等方法填充缺失值 - 插值法:使用插值法估计缺失值 - 预测法:使用机器学习模型预测缺失值

21.4.2 异常值处理

异常值是指与其他数据点显著不同的观测值,处理异常值的方法有很多,常用的方法包括: - 删除异常值:直接删除包含异常值的行或列 - 替换异常值:使用均值、中位数、众数等方法替换异常值 - 转换异常值:使用对数变换、平方根变换等方法转换异常值 - 分箱法:将异常值分到其他类别中 - 使用机器学习模型预测异常值 - 使用聚类算法识别异常值

21.4.3 重复值处理

重复值是指数据集中存在多次重复的观测值,处理重复值的方法有很多,常用的方法包括:

  • 删除重复值:直接删除重复的行
  • 合并处理:将重复值合并为一个观测值 (需要标注重复次数,以便作为权重或后续恢复数据)
  • 是否存在误标记问题 (独立报表 v.s. 合并报表)

21.5 参考资料