共计 8005 个字符,预计需要花费 21 分钟才能阅读完成。
方法论
OSEMN(Obtain, Scrub, Explore, Model, iNterpret)是一种常用的数据分析方法论,包括数据获取、数据清洗、数据探索、建模和解释等步骤。OSEMN 方法论强调了数据分析过程中数据质量和数据探索的重要性。
数理统计
数理统计分为如下两类:
- 描述统计
- 推断统计
描述性统计分析
描述性统计所提取统计的信息,称为 统计量,主要包括以下几个方面:
- 频数与频率
-
集中趋势分析
- 均值
- 中位数
- 众数
- 分位数
-
离散程度分析
- 极差
- 方差
- 标准差
-
分布形状
- 偏度
- 峰度
频数与频率
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
import warnings
## 设置 seaborn 绘图样式与字体规模
sns.set(style="darkgrid", font_scale=1.2)
plt.rcParams["font.family"] = "SimHei" # 设置中文字体
plt.rcParams["axes.unicode_minus"] = False # 是否使用 Unicode 字符集中的负号
warnings.filterwarnings("ignore") # 忽略警告信息
iris = load_iris() # 加载鸢尾花数据集
print(iris.data.shape) # NumPy 数组 (150, 4)
print(iris.target.shape) # NumPy 数组 (150,) 每朵鸢尾花对应的类别,取值为 0,1,2
print(iris.feature_names) # 特征列的名称
print(iris.target_names) # 鸢尾花类别的名称,即数值 0,1,2 分别对应的类别
## 合并数据
data = np.concatenate([iris.data, iris.target.reshape(-1, 1)], axis=1) # 横向拼接,- 1 的作用是自动计算行
data = pd.DataFrame(data, columns=["sepal_length", "sepal_width", "petal_length", "petal_width", "type"])
data.sample(10) # 随机抽样
frequency = data["type"].value_counts() # 计算类别出现的频数
print(frequency)
percentage = frequency * 100 / len(data) # 计算类别出现的频率
print(percentage)
sns.countplot(x="type", data=data) # 绘制 type 列不同取值出现的次数
plt.show()
集中趋势
mean = data["sepal_length"].mean() # 花萼长度的均值
median = data["sepal_length"].median() # 中位数
s = data["sepal_length"].mode() # mode 方法返回 Series 类型
mode = s.iloc[0] # 众数
print(mean, median, mode)
sns.distplot(data["sepal_length"]) # 绘制数据的发布,直方图 + 密度图
## 绘制垂直线
plt.axvline(mean, ls="-", color="r", label=" 均值 ")
plt.axvline(median, ls="-", color="g", label=" 中值 ")
plt.axvline(mode, ls="-", color="indigo", label=" 众数 ")
plt.legend()
x = [1, 3, 10, 15, 18, 20, 23, 40]
print(np.quantile(x, q=[0.25, 0.5, 0.75])) # 计算分位数
print(np.percentile(x, q=[25, 50, 75]))
s = pd.Series(x)
print(s.describe()) # pandas 中计算分位数
print(s.describe(percentiles=[0.25, 0.9]))
离散程度
sub = np.ptp(data["sepal_length"]) # 极差
var = data["sepal_length"].var() # 方差
std = data["sepal_length"].std() # 标准差
print(sub, var, std)
plt.figure(figsize=(15, 4)) # 设置图形大小,宽 * 高,单位为英寸
plt.ylim(-0.5, 1.5)
plt.plot(data["petal_length"], np.zeros(len(data)), linestyle="", marker="o", markersize=10, color="g", label=" 花瓣长度 ")
plt.plot(data["petal_width"], np.ones(len(data)), ls="", marker="o", ms=10, color="r", label=" 花瓣宽度 ")
plt.axvline(data["petal_length"].mean(), ls="--", color="g", label=" 花瓣长度均值 ")
plt.axvline(data["petal_width"].mean(), ls="--", color="r", label=" 花瓣宽度均值 ")
plt.legend()
分布形状
## 构造左偏分布数据
t1 = np.random.randint(1, 11, size=100)
t2 = np.random.randint(11, 21, size=500)
t3 = np.concatenate([t1, t2])
left_skew = pd.Series(t3)
## 构造右偏分布数据
t1 = np.random.randint(1, 11, size=500)
t2 = np.random.randint(11, 21, size=100)
t3 = np.concatenate([t1, t2])
right_skew = pd.Series(t3)
print(left_skew.skew(), right_skew.skew())
## 绘制核密度图
sns.kdeplot(left_skew, shade=True, label=" 左偏 ")
sns.kdeplot(right_skew, shade=True, label=" 右偏 ")
plt.legend()
## 标准正态分布
standard_normal = pd.Series(np.random.normal(0, 1, size=10000))
print(" 标准正态分布峰度:", standard_normal.kurt(), " 标准差:", standard_normal.std())
sns.kdeplot(standard_normal, label=" 标准正态分布 ")
sns.kdeplot(data["sepal_width"], label=" 花萼宽度 ")
sns.kdeplot(data["petal_length"], label=" 花瓣长度 ")
plt.legend()
推断统计分析
总体的参数往往是未知的,为了获取总体的参数,就需要通过样本统计量来估计总体参数。
参数估计
点估计
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
import warnings
sns.set(style="darkgrid", font_scale=1.2)
plt.rcParams["font.family"] = "SimHei"
plt.rcParams["axes.unicode_minus"] = False
warnings.filterwarnings("ignore")
iris = load_iris()
data = np.concatenate([iris.data, iris.target.reshape(-1, 1)], axis=1)
data = pd.DataFrame(data, columns=["sepal_length", "sepal_width", "petal_length", "petal_width", "type"])
print(data["petal_length"].mean())
点估计容易受到随机抽样的影响,可能无法保证结论的准确性。但是,样本来自于总体,样本还是能够体现出总体的一些特征的。
区间估计
区间估计使用一个置信区间与置信度,表示总体参数有多少可能(置信度)会在该范围(置信区间)内。
样本均值分布的标准差,称为标准误差,简称标准误。
根据中心极限定理,如果多次抽样 (总体的均值为 μ,方差为σ*σ
),则样本均值构成的正态分布,满足样本均值~N(μ, σ*σ/n
),如果只对总体进行一次抽样,则该样本的均值有 95% 的概率会在(μ-2σ, μ+2σ) 范围内,仅有 5% 的概率会在 (μ-2σ, μ+2σ) 范围外。根据小概率事件 (很小的概率在一次抽样中基本不会发生),如果抽样的样本均值在(μ-2σ, μ+2σ) 之外,就可以认为,本次抽样来自的总体,该总体的均值并非是我们所期望的均值。
通常,以 2 倍标准差作为判依据,即以样本均值为中心,正负 2 倍标准差构成的区间,就是置信区间。而 2 倍标准差包含了 95% 的数据,因此,此时的置信度为 95%。换言之,有 95% 的信心认为总体的均值会在置信区间之内。
a = np.array([0.497, 0.506, 0.518, 0.524, 0.498, 0.511, 0.520, 0.515, 0.512])
mean, std = 0.5, 0.015 # 总体均值与标准差
sample_mean = a.mean() # 样本均值
se = std / np.sqrt(len(a)) # 标准误
left, right = sample_mean - 1.96 * se, sample_mean + 1.96 * se
print(f" 置信区间:({left:.3f}, {right:.3f})")
plt.plot(mean, 0, marker="*", color="orange", ms=15, label=" 总体均值 ")
plt.plot(sample_mean, 0, marker="o", color="r", label=" 样本均值 ")
plt.hlines(0, xmin=left, xmax=right, color="b", label=" 置信区间 ")
plt.axvline(left, 0.4, 0.6, color="r", ls="--", label=" 左边界 ")
plt.axvline(right, 0.4, 0.6, color="g", ls="--", label=" 右边界 ")
plt.legend()
假设检验
-
设置原假设与备择假设
- 原假设:μ=μ0=0.5kg(机器运作正常)
- 备择假设:μ≠μ0≠0.5kg(机器运作不正常)
- 设置显著性水平,设置 α =0.05
- 根据问题选择假设检验的方式。袋装糖的净重呈正态分布,总体标准差已知,选择 Z 检验
- 计算统计量,并通过统计量获取 P 值。根据选择的假设检验,进行计算
Z 检验
Z 检验用于正态总体均值的检验,即用来检验总体的均值 (末知) 是否与假设的均值相同。
from scipy import stats
a = np.array([0.497, 0.506, 0.518, 0.524, 0.498, 0.511, 0.520, 0.515, 0.512])
mean, std = 0.5, 0.015 # 总体均值与标准差
sample_mean = a.mean() # 样本均值
se = std / np.sqrt(len(a)) # 标准误
Z = (sample_mean - mean) / se # Z 统计量
print(" 统计量 Z:", Z)
## 计算 P 值
## sf 函数,根据参数指定的统计量,返回该统计量与右侧图像围成的面积值
## 实际上,P 值就是通过概率密度函数计算出来的面积,即概率值
P = 2 * stats.norm.sf(abs(Z))
print("P-Value 值:", P)
由结果可知,Z 值偏离超过了 2 倍的标准差,P 值也就小于 0.05。因此,在显著性水平 α =0.05 下,P<α,故拒绝原假设,接受备择假设,即认为机器运作是不正常的。
右边假设检验
右边假设检验的拒绝域在正态分布的右侧区域。
- 原假设:μ≤μ0=0.5kg(机器运作正常)
- 备择假设:μ>μ0≠0.5kg(机器运作不正常)
## Z 统计量与之前计算的方式相同
P = stats.norm.sf(Z)
print("P-Value 值:", P)
P<α,拒绝原假设,认为机器运作是不正常的。
左边假设检验
左边假设检验的拒绝域在正态分布的左侧区域。
- 原假设:μ≥μ0=0.5kg(机器运作正常)
- 备择假设:μ<μ0≠0.5kg(机器运作不正常)
## cdf 函数,返回该统计量与左侧图像围成的面积值
P = stats.norm.cdf(Z)
print("P-Value 值:", P)
P>α,维持原假设,认为机器运作是正常的。
假设检验的两类错误
接受 H0 | 拒绝 H0 | |
---|---|---|
H0 为真 | 正确 | I 类错误(α 错误,去真错误) |
H0 为假 | II 类错误(β 错误,取伪错误) | 正确 |
t 检验
与 Z 检验的应用场景类似,t 检验也用于正态总体均值的检验。只不过,t 检验的正态总体,标准差是未知的。
t 检验分为三种类型:
- 单样本 t 检验
- 两独立样本 t 检验
- 两配对样本 t 检验
- t 检验原假设为等值假设,备择假设为不等值假设(双边检验)
- t 检验通过构造 t 统计量来实现
- t 统计量服从 t 分布
- 随着样本容量的增大,t 分布逐渐接近于标准正态分布
单样本 t 检验
单样本 t 检验 前提条件为:
- 总体服从正态分布
-
总体方差 (标准差) 末知
mean = data["petal_length"].mean() std = data["petal_length"].std() t = (mean - 3.5) / (std / np.sqrt(len(data))) # t 统计量 print("t 统计量:", t) P = 2 * stats.t.sf(abs(t), df=len(data["petal_length"]) - 1) print("P-Value 值:", P) ## 此外,也可通过 scipy 提供的相关方法来进行 t 检验的计算,无需自行计算 stats.ttest_1samp(data["petal_length"], 3.5)
两独立样本 t 检验
两独立样本 t 检验 用于检验两个正态总体的均值是否相同,其前提条件为:
- 1. 两个样本是独立的
- 2. 两个样本所在的总体服从正态分布
- 3. 总体方差未知
由于方差是否齐性(相同),会影响 t 统计量的计算。因此,在进行两独立样本 t 检验时,首先需要进行方差齐性检验。可以通过 Levene 检验来判断两个正态总体的方差是否相同。
## A,B 两个学校的学生数据
school_A = np.array([169.7, 163.1, 179.0, 163.4, 180.2, 160.9, 177.3, 160.7, 169.9, 163.4])
school_B = np.array([163.6, 180.0, 171.8, 174.7, 164.3, 173.9, 167.0, 162.9, 164.2, 171.6])
print(school_A.mean(), school_B.mean())
## 使用 levene 检验判断两个样本背后的总体,其方差是否一致
stats.levene(school_A, school_B)
P>0.05,因此可以认为两个总体的方差是相同的。然后,就可以使用两独立样本 t 检验,来验证两个正态总体的均值是否相同。
stats.ttest_ind(school_A, school_B, equal_var=False)
P>0.05,因此维持原假设,认为两个正态总体的均值是相同的,即 A 与 B 两个学校的平均身高相同。
两配对样本 t 检验
两配对样本 t 检验,是同一组受试者产生两组数据,然后,验证两组数据是否存在显著性差异的方式。两组数据可以是:
- 受试者在接受处理前与处理后的数据
- 受试者在接受两种不同的处理方式,分别产生的数据
两配对样本 t 检验的前提条件为 :
- 配对样本差值独立
- 配对样本差值服从正态分布
- 总体标准差末知
## 培训前体重数据
train_before = np.array([98.8, 92.0, 94.9, 101.2, 99.3, 85.1, 94.8, 89.2, 89.5, 92.1])
## 培训后体重数据
train_after = np.array([88.4, 92.4, 90.3, 88.4, 89.3, 89.0, 92.5, 87.4, 88.9, 85.4])
## 两配对样本 t 检验(双边检验)stats.ttest_rel(train_before, train_after)
P<0.05,拒绝原假设,认为体重在培训前跟培训后是不同的。
如果没有充足的证据证明减肥有效,则应该按照减肥无效处理。则:
- 原假设:D≤0(减肥无效)
- 备择假设:D>0(减肥有效)
sta = stats.ttest_rel(train_before, train_after).statistic # 获取统计量
P = stats.t.sf(sta, df=len(train_before) - 1)
print(P)
P<0.05,拒绝原假设,认为减肥有效。
卡方检验
卡方检验用于检测变量的理论频数与实际频数是否一致。
卡方检验可以分为两个类别 :
- 单个变量
- 两个变量
关于卡方检验,说明如下 :
- 卡方检验原假设为理论频数与实际频数一致,备择假设为理论频数与实际频数不一致(双边假设)
- 卡方检验为非参数检验,不对总体进行任何假设,因此也不涉及关于总体分布的参数
- 卡方检验通过卡方统计量来实现
- 卡方统计量服从卡方分布
单个变量
卡方检验 (单个变量) 的前提条件为 :
- 观测值之间相互独立
- 变量每个取值的理论频数与实际频数≥5
举例:某手机在一段时间内,销量为 200,各种颜色销量如下表所示:
红色 | 绿色 | 黄色 | 紫色 | 白色 |
---|---|---|---|---|
43 | 50 | 24 | 31 | 52 |
请问该款手机的颜色对销售是否会造成影响?
o = [43, 50, 24, 31, 52]
e = [40, 40, 40, 40, 40]
stats.chisquare(f_obs=o, f_exp=e)
P<0.05,拒绝原假设,认为理论频数与实际频数不一致,得出结论:手机颜色对销售确实造成了影响。
两个变量
举例:某手机在一段时间内,每种颜色与性别的销量如下表所示:
红色 | 绿色 | 黄色 | 紫色 | 白色 | |
---|---|---|---|---|---|
男 | 43 | 50 | 24 | 31 | 52 |
女 | 38 | 42 | 30 | 29 | 45 |
请问该款手机的购买用户中,性别与手机颜色是否具有关联?
根据随机变量独立性的定义,如果两个随机变量 A 与 B 是独立的,则有:
P(AB) = P(A)*P(B)
因此,可以根据该定义来计算在性别与颜色两个变量独立的情况下,每个变量取值的期望概率(理论概率。例如,计算性别为男,颜色为红色为例。
P(A= 男, B= 红色) = P(A= 男) * P(B= 红色)
=(43 + 50 + 24 + 31 + 52) / 384 * (43 + 38) / 384
=0.52083 * 0.2109
=0.10986328125
有了期望概率,就可以很容易计算每个取值的期望频数:
Expected(A= 男, B= 红色) = count * P(A= 男, B= 红色)
=384 *0.10986328125
=42.1875
observed = [[43, 50, 24, 31, 52], [38, 42, 30, 29, 45]]
stats.chi2_contingency(observed)
P>0.05,维持原假设,认为理论频数与实际频数一致(意味着两个变量是独立的),得出结论:性别对手机颜色没有影响,他们之间没有关系。