单变量数据分析

研究方法
Python
作者

yangjh

发布日期

November 27, 2022

描述统计(Descriptive Statistics)是定量地描述或总结样本中变量特征的统计方法。分析资料时,首先要用适当的描述统计方法来简化资料中的各个变量,然后进一步分析变量之间的关系。按照变量的数量,描述统计可以分为:单变量描述统计、双变量描述统计、多变量描述统计。

单变量数据分析是指对单个变量进行分析的过程,它是数据分析中的一个基础环节。这种分析可以提供对数据的基本理解,包括数据的分布、中心趋势、离散程度等。在进行单变量分析时,重要的是要选择适合该变量类型(连续变量、分类变量等)的方法和工具。这种分析通常是数据探索的起点,为多变量分析和更复杂的统计分析打下基础。

单变量描述统计

单变量描述统计分析按照变量的测量层次,可以分为无序类别变量的描述统计分析、有序类别变量的描述统计分析以及数值变量的描述统计分析。

单变量描述统计的维度有:

  • 集中趋势。如众数、平均值、中位数等。
  • 离散特征。如异质指数、四分位差、方差、标准差。
  • 分布特征。如偏度、峰度。

无序类别变量的描述统计

适用于无序类别变量的描述统计方法,有次数分布、比例、比率、图示和对比值等。

次数分布

次数分布(frequency distribution),就是变量中每一个值的出现次数。通过次数分布,可以看出变量中不同取值的多少。不同规模样本中的次数分布不能直接比较。

比例

比例(proporitions) 就是将每个类别的次数 \(f\) 除以总数\(N\)。公式为:\(P = \dfrac{f}{N}\)。通过计算比例,可使不同样本规模的总数成为同一个基数,即都是1

比率

比率(rates)就是把比例的基数变大,使读者更容易领会。社会科学中最常用的比率是百分率(%),在汇报结果时,通常保留12位小数。

众值

适合无序类别变量的集中趋势指标是众值(mode)。众值就是出现次数最多的值。使用众值预测所犯的错误总数是最少的。

类别数据可视化

在社会科学中多使用柱状图和饼图描述无序类别变量。

import pandas as pd

# 加载数据
file_path = R'../../data/csv/数据清理演示文件.csv'  
data = pd.read_csv(file_path)

# 计算次数和比率(百分比)
frequency = data['政治面貌'].value_counts()
rates = data['政治面貌'].value_counts(normalize=True) * 100

# 将次数和比率合并为一个 DataFrame
summary_df = pd.DataFrame({'次数': frequency, '比率 (%)': rates})

# 添加合计行
summary_df.loc['合计'] = summary_df.sum()

# 计算众数
mode = data['政治面貌'].mode()[0]

# 显示结果
print(summary_df)
print('政治面貌的众数为:', mode)
         次数      比率 (%)
政治面貌                   
团员    635.0   73.410405
党员    113.0   13.063584
群众     96.0   11.098266
其他     21.0    2.427746
合计    865.0  100.000000
政治面貌的众数为: 团员
import plotly.express as px

# 创建柱状图
fig_bar = px.bar(frequency, 
                 title="政治面貌分布 - 柱状图", 
                 labels={'index': '政治面貌', 'value': '数量'})

# 显示图形
fig_bar.show()

# 创建饼图
fig_pie = px.pie(frequency, 
                 names=frequency.index, 
                 values=frequency.values, 
                 title="政治面貌分布 - 饼图")

# 显示图形
fig_pie.show()

有序类别变量的描述统计

适用于无序类别变量的描述统计方法,如次数分布、比例、比率、柱状图、饼图,也适用于有序类别变量。还有针对有序类别变量的描述统计方法:累计次数、累加百分率。

累加次数

累加次数(cumlulative frequencies),简写cf,就是把次数逐级相加起来。分为两种,一种是向上累加,一种是向下累加。累加次数的作用是使我们容易知道某值以下或者以上的次数总和。

累加百分比

累加百分比(cumulative percentages),简写c%,就是将各级的百分率数值逐级相加。

# 计算【会打多少分】这个定序类别变量的相关统计量

# 计算次数分布和比率(百分比)
score_counts = data['会打多少分'].value_counts()
score_rates = data['会打多少分'].value_counts(normalize=True) * 100

# 计算累加次数和累加比例
cumulative_counts = score_counts.cumsum()
cumulative_proportions = score_rates.cumsum()

# 合并所有计算结果到一个 DataFrame
score_summary_df = pd.DataFrame({
    '次数': score_counts,
    '比率 (%)': score_rates,
    '累加次数': cumulative_counts,
    '累加比率 (%)': cumulative_proportions
})

# 添加合计行
score_summary_df.loc['合计'] = score_summary_df.sum(numeric_only=True)
# 移除累加比率的求和
score_summary_df.loc['合计', '累加比率 (%)'] = 100.0

score_summary_df
次数 比率 (%) 累加次数 累加比率 (%)
会打多少分
六十到八十 413.0 47.580645 413.0 47.580645
八十到一百 237.0 27.304147 650.0 74.884793
四十到六十 163.0 18.778802 813.0 93.663594
20~40 28.0 3.225806 841.0 96.889401
零到二十 27.0 3.110599 868.0 100.000000
合计 868.0 100.000000 3585.0 100.000000

数值变量的描述统计

使用均值、中位数、众数描述集中趋势:

  • 均值(Mean):所有数值的总和除以数值的个数。它提供了数据集的平均水平。
  • 中位数(Median):将数据集从小到大排列后位于中间位置的数值。对于偏态分布的数据,中位数是一个更好的中心趋势度量。
  • 众数(Mode):数据集中出现次数最多的数值。对于分类数据和离散数据非常有用。

使用范围、四分位数、标准差、方差描述离散趋势:

  • 范围(Range):最大值和最小值之间的差异。
  • 四分位数范围(Interquartile Range, IQR):第25百分位数(Q1)和第75百分位数(Q3)之间的差,用于衡量中间50%数据的离散程度。
  • 标准差(Standard Deviation):度量数据点与均值的平均距离。
  • 方差(Variance):标准差的平方,表示数据的总体变异性。

使用偏度、峰度描述分布形状:

  • 偏度(Skewness):度量数据分布的不对称性。正偏度表示数据向右偏斜,负偏度表示向左偏斜。
  • 峰度(Kurtosis):描述数据分布的尖锐程度。高峰度表示数据分布比正态分布更尖锐。

使用直方图、箱型图进行可视化描述:

  • 直方图(Histogram):展示数据的频率分布。
  • 箱形图(Box Plot):展示数据的中位数、四分位数以及异常值。
import pandas as pd
import numpy as np
import plotly.express as px

# Load the data
file_path = R'../../data/csv/600519.csv'  
data = pd.read_csv(file_path)

# Descriptive statistics for 'close'
mean_close = data['close'].mean()
median_close = data['close'].median()
mode_close = data['close'].mode()[0]

# Dispersion measures for 'close'
range_close = data['close'].max() - data['close'].min()
iqr_close = np.percentile(data['close'], 75) - np.percentile(data['close'], 25)
std_dev_close = data['close'].std()
variance_close = data['close'].var()

# Shape measures for 'close'
skewness_close = data['close'].skew()
kurtosis_close = data['close'].kurtosis()

# Print the summary statistics
print("Descriptive Statistics for 'Close':")
print(f"Mean: {mean_close}")
print(f"Median: {median_close}")
print(f"Mode: {mode_close}")
print(f"Range: {range_close}")
print(f"Interquartile Range (IQR): {iqr_close}")
print(f"Standard Deviation: {std_dev_close}")
print(f"Variance: {variance_close}")
print(f"Skewness: {skewness_close}")
print(f"Kurtosis: {kurtosis_close}")

# Creating Histogram
histogram = px.histogram(data, x='close', nbins=50, title='Histogram of Close Prices')
histogram.show()

# Creating Boxplot
boxplot = px.box(data, y='close', title='Box Plot of Close Prices')
boxplot.show()
Descriptive Statistics for 'Close':
Mean: 182.3996800976801
Median: 159.98
Mode: 38.0
Range: 778.3100000000001
Interquartile Range (IQR): 161.965
Standard Deviation: 165.99021981910192
Variance: 27552.75307559378
Skewness: 1.8917965620486012
Kurtosis: 3.560459084792395

偏度 (Skewness) = 1.8917965620486012: 偏度值大于 1,这表明数据分布是正偏的(右偏),意味着数据的右尾(更大的值)比左尾(更小的值)长。在这种情况下,数据的平均值通常大于中位数。

峰度 (Kurtosis) = 3.560459084792395: 峰度是衡量数据分布尖锐程度的统计量。峰度值 3 表示数据分布比正态分布更尖峭,具有更厚的尾部。这可能意味着数据中存在异常值或极端变化。

总的来说,您的数据表现出明显的正偏和高峰度特征,这可能影响数据分析和统计推断。处理这种数据时,可能需要考虑使用适应非正态分布的方法和技术

单变量推论统计

单变量的推论统计,可以分为参数估计和假设检验两大类。

单变量参数估计

单个总体均值的估计

当假设数据来自正态分布或大样本时,可以使用stats.t.interval计算置信区间。

例如,我们可以计算豆瓣电影数据中用户打分的平均值在95%置信水平下的置信区间。

import pandas as pd
from scipy import stats
# 打开数据文件
file_path = R"../../data/csv/movie_data_cleaned.csv"
df_movies = pd.read_csv(file_path)
# 计算均值和标准误差
mean = df_movies['average'].mean()
std_error = stats.sem(df_movies['average'])
# 设定置信水平
confidence_level = 0.95
# 设定自由度
df = len(df_movies['average']) - 1
# 计算置信区间
confidence_interval = stats.t.interval(confidence_level, df, loc=mean, scale=std_error)
# 输出结果
print(F"均值:{mean: .2f}")
print(F"均值在置信水平{confidence_level}下的置信区间为:", confidence_interval)
均值: 7.20
均值在置信水平0.95下的置信区间为: (7.171337577138237, 7.2214184794700635)

单个总体比例的估计

可以使用二项分布和正态分布的近似方法计算单个总体比例的置信区间。

例如:想要调查某个群体成员的态度,用95%的置信水平估计该群体中态度打分为”八十到一百”的人数比例的置信区间。

import pandas as pd
import statsmodels.api as sm

# 读取数据
file_path = R'../../data/csv/数据清理演示文件.csv'  
data = pd.read_csv(file_path)


target_value = '八十到一百'  # 要计算置信区间的目标取值
confidence_level = 0.95  # 置信水平

# 计算某个取值的出现次数
count_target_value = data[data['会打多少分'] == target_value].shape[0]

# 计算置信区间
conf_interval = sm.stats.proportion_confint(count_target_value, data.shape[0], alpha=1-confidence_level)

# 打印置信区间
print(f"{confidence_level * 100}% 置信区间 ({target_value}):{conf_interval}")
95.0% 置信区间 (八十到一百):(0.2332341917623141, 0.2905227143150339)

单个总体方差的估计

在市场研究中,估计总体方差可以用于确定市场需求的不确定性。这有助于企业制定市场战略和预测市场表现。在金融领域,估计总体方差可以用于评估不同投资组合的风险。较高的方差通常表示投资风险较大,而较低的方差表示投资更为稳定。

下面的例子中,将使用stats.t.interval函数计算收盘价的总体方差的置信区间。

import pandas as pd
import numpy as np
from scipy import stats

file_path = R'../../data/csv/600519.csv'  
data = pd.read_csv(file_path)

# 指定置信水平和自由度(通常自由度为样本大小减去1)
confidence_level = 0.95
df_degrees_of_freedom = data.shape[0] - 1

# 使用pandas的var方法计算样本方差
sample_variance = data['close'].var()

# 使用stats.t.interval函数计算置信区间
confidence_interval = stats.t.interval(confidence_level, df_degrees_of_freedom, loc=sample_variance, scale=np.sqrt(sample_variance / data.shape[0]))

# 打印总体方差的置信区间
print(f"变量的方差为:{sample_variance: .2f}{confidence_level * 100}% 置信区间:({confidence_interval[0]:.2f}, {confidence_interval[1]:.2f})")
变量的方差为: 27552.75,95.0% 置信区间:(27547.67, 27557.84)

单变量假设检验

类别变量的拟合优度检验

类别变量的拟合优度检验(Goodness-of-Fit Test for Categorical Variables)是一种统计方法,用于判断观察到的类别数据分布是否与某个特定理论分布相符合。这种检验通常用于检查样本数据是否符合预期的分布,比如均匀分布、二项分布或任何其他特定的理论分布。

最常见的拟合优度检验是卡方(Chi-Square)拟合优度检验。它的基本步骤包括:

  1. 设立假设

    • 零假设 \(H_0\):观测频数分布与预期频数分布没有显著差异。
    • 备择假设 \(H_1\):观测频数分布与预期频数分布有显著差异。
  2. 计算卡方统计量:这一步涉及观测频数和理论(预期)频数之间的比较。公式如下:

    \[\chi^2 = \sum \frac{(O_i - E_i)^2}{E_i}\]

    其中,\(O_i\) 是第 \(i\) 个类别的观测频数,\(E_i\) 是第 \(i\) 个类别的预期频数。

  3. 确定显著性水平:通常选择 0.05(5%)或 0.01(1%)作为显著性水平。

  4. 决定拒绝或不拒绝零假设:通过比较计算出的卡方统计量与卡方分布的临界值来决定。如果计算值大于临界值,则拒绝零假设,表明观测分布与理论分布有显著差异。

卡方拟合优度检验在Python中,可以使用 Scipy 包中的chisquare方法实现。

期望频数相等

多数情况下,我们需要对某个类别变量已有的类别进行拟合优度检验,其中一种情况是检验每个类别的比例是否存在显著性差异。在下面的检验中,我们将假设大一到大四各年级的比例相同(即每个年级的学生人数是一样的),然后检验实际观察到的数据是否与这一假设相符。

from scipy.stats import chisquare
file_path = R'../../data/csv/数据清理演示文件.csv'  
data = pd.read_csv(file_path)

# 选择只包含大一到大四的数据
valid_grades_standard = data[data['年级'].isin(['大一', '大二', '大三', '大四'])]

# 计算大一到大四的频数
grade_counts_standard = valid_grades_standard['年级'].value_counts()

# 进行卡方拟合优度检验
chi2, p_value = chisquare(grade_counts_standard)
print(F"卡方统计量: {chi2: .2f}    p值:{p_value: .4f}")
卡方统计量:  135.40    p值: 0.0000

这个极低的P值表明,在大一到大四之间的学生人数比例存在显著差异。换句话说,这四个年级的学生数量并不相同。当然,这个例子中年级比例直觉看就存在巨大差异。这里只是使用统一的演示数据进行案例分析。

期望频数不相等

有时,我们会遇到类别的期望频数不相等的检验分析需求。比如,想了解样本中的汉族的比例,是否与期望频率40%一致。

from scipy.stats import chisquare
import numpy as np

# 读取数据
file_path = R'../../data/csv/数据清理演示文件.csv'  
data = pd.read_csv(file_path)

# 统计每个民族的数量
ethnic_counts = data['民族'].value_counts()

# 计算总样本数
total_samples = len(data)

# 实际的汉族数量
han_count = ethnic_counts.get('汉族', 0)

# 实际的汉族比例
actual_han_ratio = han_count / total_samples

# 期望的汉族比例
expected_han_ratio = 0.40

# 期望和实际观测值
observed = np.array([han_count, total_samples - han_count])
expected = np.array([expected_han_ratio * total_samples, (1 - expected_han_ratio) * total_samples])

# 拟合优度检验
chi_square_stat, p_value = chisquare(f_obs=observed, f_exp=expected)

chi_square_stat, p_value, actual_han_ratio, expected_han_ratio
(0.11510128913443832, 0.7344094973080428, 0.40552486187845305, 0.4)

拟合优度检验p值为0.73,远高于常用的显著性水平(例如0.05),这表明实际观测到的汉族比例与期望的40%之间没有显著差异。因此,我们不能拒绝原假设,即实际的汉族比例与期望的40%一致。这意味着根据这份数据,汉族的比例与预期的40%相符。

单个总体均值的检验

检验总体均值与某个假设值之间的差异是否显著。

下面的例子中,我们将计算南京鼓楼区的房价是否与指导价35000存在显著性差异。

statsmodelsztest 函数中,alternative 参数用于指定检验的备择假设的类型。这个参数影响着如何解释检验结果,特别是 P 值的计算。alternative 参数可以有以下几种设定:

  1. alternative='two-sided': 这是默认设置,表示执行双尾检验。在这种情况下,备择假设是样本均值不等于指定的值(在这个例子中为35000)。换句话说,这种设置用于检测样本均值是否显著高于或低于35000。
  2. alternative='larger': 这表示执行单尾检验,备择假设是样本均值大于指定的值(35000)。在这种情况下,P值表示样本均值小于或等于35000的概率。
  3. alternative='smaller': 这同样表示执行单尾检验,但备择假设是样本均值小于指定的值(35000)。在这种情况下,P值表示样本均值大于或等于35000的概率。

在这个案例中,我们需要选择’two-sided’。

from statsmodels.stats.weightstats import ztest as sm_ztest

# 读取数据
file_path = R'../../data/csv/Xiaoqu_NJ_format.csv'  
data = pd.read_csv(file_path)

# 再次提取鼓楼区域的参考均价数据
# 这里需要确保参考均价是数值型数据
gulou_data = data[data['区县定位'] == '鼓楼']['参考均价'].dropna()

# 使用statsmodels的ztest进行Z检验
z_stat, p_value = sm_ztest(gulou_data, value=35000, alternative='two-sided')

print(F"Z值为:{z_stat: .2f}, p值为{p_value: 0.4f}")
Z值为: 11.37, p值为 0.0000

单个总体比例的检验

单个总体比例的检验,当样本规模大于30时,可以采用Z检验。

单个变量的总体比例的Z检验和拟合优度检验都是用来评估样本数据是否与某种预期或理论分布一致的统计方法。然而,它们在应用和假设方面有所不同:

  1. 单个变量的总体比例的Z检验
    • 这种检验通常用于比较单个二分类变量(如是/否,成功/失败)的样本比例与一个已知的总体比例。
    • 比如,如果你想知道一个样本中成功的比例是否显著不同于总体中已知的成功比例,可以使用这个检验。
    • 该检验假设样本来自一个大的总体,并且样本中的观察是独立的。
    • 它使用Z统计量来决定样本比例与总体比例之间是否存在显著差异。
  2. 拟合优度检验(如卡方拟合优度检验)
    • 拟合优度检验用于确定一个样本的分布是否与特定的理论分布一致。
    • 这个检验更通用,可以用于单个变量,也可以是多个类别(不仅限于二分类)。
    • 比如,它可以用来检验掷骰子的结果是否服从均匀分布,或者一个样本中的种族分类是否符合某个已知的人口分布。
    • 拟合优度检验通常使用卡方统计量来判断样本分布与理论分布之间是否存在显著差异。

简而言之,总体比例的Z检验专注于单个二分类变量的比例与已知总体比例的比较,而拟合优度检验关注的是样本分布是否与一个特定的理论分布一致,适用于多种类型的变量和分布。

from statsmodels.stats.weightstats import ztest

# 读取数据
file_path = R'../../data/csv/数据清理演示文件.csv'  
data = pd.read_csv(file_path)

# 将数据转换为适用于ztest的格式
# 1代表汉族,0代表非汉族
binary_ethnic_data = data['民族'].apply(lambda x: 1 if x == '汉族' else 0)

# 进行ztest
# value参数指定零假设下的比例,这里是0.50
z_stat, p_value_ztest = ztest(binary_ethnic_data, value=0.50)

z_stat, p_value_ztest
(-5.785303670632118, 7.238145312421245e-09)

正态性检验

检验样本是否来自正态总体,可采用K-S检验。该检验既可以用于大样本,也可用于小样本。

K-S检验(Kolmogorov-Smirnov检验)是一种非参数检验方法,用于判断两个样本是否来源于同一个分布,或者一个样本是否来自于特定的分布。它主要通过比较累积分布函数(CDF)来进行检验。K-S检验是非参数检验,不需要假设数据遵循特定的分布(如正态分布)。适用于小样本和大样本。但对样本中的离群值敏感,且在样本量较大时对小的差异也可能敏感。

K-S检验要求样本数据是连续数值。

import scipy.stats as stats

file_path = R'../../data/csv/movie_data_cleaned.csv'  
data = pd.read_csv(file_path)
from scipy import stats

ks_statistic, p_value = stats.kstest(data['average'], 'norm', args=(data['average'].mean(), data['average'].std()))

ks_statistic, p_value
(0.08866981373038102, 1.720113882835582e-57)

正态分布,还可以通过直方图进行观察。

import plotly.express as px

fig = px.histogram(data, x='average', nbins=50, title='Histogram of "average" Ratings')
fig.show()

回到顶部