简介:这份资源是面向SPSS初学者与进阶学习者的数据分析配套数据文件包,围绕《SPSS22.0从入门到精通》等教程整理,帮助读者在真实数据上练习从数据清洗到统计建模的完整流程。压缩包共84个文件,以74个sav数据文件为主,另含6个xls表格、2个txt文本、1个xml目录和1个mdb数据库,整体约461KB,覆盖描述统计、t检验、方差分析、相关与回归、聚类判别等章节案例,并配有医学、农业、教育等场景数据。资源按chapter02至chapter19分章组织,目录结构清晰,便于对照教程逐章实操,快速理解不同统计方法的应用场景与结果解读。目前已有1378人学习下载,适合希望借助现成数据文件巩固SPSS操作、积累分析经验的高校学生与数据分析从业者。
1. 拿到一份 SPSS 数据文件,先别急着点“分析”
很多人拿到.sav文件的第一反应是双击打开,然后直奔“分析”菜单跑个描述统计,结果要么变量名全是VAR00001,要么日期字段被识别成字符串,要么中文乱码。我见过太多人在这第一步就翻车,后面跑出来的结果自己都不敢信。SPSS 数据文件(.sav)本质上是一个带元数据的二进制容器,里面不只有数据值,还有变量标签、值标签、缺失值定义、测量尺度、格式宽度这些“说明书”信息。换句话说,它比 CSV 多了一层语义层,这层语义层用好了是效率工具,用不好就是黑匣子。这份资源适合谁?适合手里已经有一份 SPSS 数据文件、需要做清洗和建模前处理、但又不想在变量定义上反复折腾的从业者。下面我从文件结构讲到实操命令,再到避坑,按能复现的路径走一遍。
2. SPSS 数据文件的内部结构与读取方式:从.sav到可分析数据框
2.1.sav文件里到底存了什么
.sav是 SPSS 的专有二进制格式,结构上大致分两部分:文件头和数据区。文件头里记录了变量数量、个案数、压缩方式、创建时间、文件标签等;变量字典里每个变量带一组长属性——变量名(Name)、变量标签(Label)、值标签(Value Labels)、缺失值(Missing Values)、测量尺度(Measure:名义/有序/标度)、列宽和对齐方式。数据区则是按个案逐行存储的实际值,可能经过压缩(bytecode 压缩或 zlib 压缩)。这就是为什么直接用文本编辑器打开.sav会看到乱码,也是为什么用pandas.read_csv读它会报错。理解这一层,后面遇到“读进来变量类型不对”就不会慌,因为问题多半出在元数据解析环节,而不是数据本身。
2.2 用 Python 读取.sav:pyreadstat与pandas的分工
常见做法是用pyreadstat读.sav,它能把数据值和元数据分开返回,比直接pandas.read_spss更可控。下面这段是我一般会先跑的探查脚本:
import pyreadstat # 读取 .sav 文件,同时拿到数据框和元数据 df, meta = pyreadstat.read_sav( "survey_data.sav", apply_value_formats=False, # 先不套用值标签,保留原始编码 formats_as_category=False, # 不自动转 category,避免后续 merge 出问题 encoding="UTF-8" # 中文环境建议显式指定,减少乱码概率 ) # 查看变量名列表和对应的变量标签 for col, label in zip(meta.column_names, meta.column_labels): print(f"{col} -> {label}") # 查看每个变量的测量尺度和缺失值定义 print(meta.original_variable_types) print(meta.missing_ranges)这段代码的逻辑说明:apply_value_formats=False是关键参数,如果设为True,pyreadstat会把值标签直接替换成文字,比如把1换成“男”、2换成“女”,看起来方便,但后续做回归或编码时反而要多一步反向映射。我一般先保留原始编码,等清洗完再决定哪些变量需要套标签。formats_as_category=False是为了避免 pandas 的 category 类型在merge或groupby时产生意料之外的排序行为。encoding参数在中文 Windows 环境下尤其重要,不指定时可能按系统默认编码解析,导致变量标签里的中文变成问号。meta.missing_ranges会告诉你哪些值被定义为缺失,比如-9、99这类编码缺失,如果不处理,它们会直接参与均值计算,结果偏得离谱。
2.3 用 R 读取.sav:haven包的参数取舍
如果团队用 R,haven::read_sav()是标准入口。它默认会把值标签保留为labelled类型,打印时显示标签,但底层仍是数值。下面是一个带参数调整的读取示例:
library(haven) library(dplyr) # 读取 .sav,指定编码并保留标签 data <- read_sav( "survey_data.sav", encoding = "UTF-8", # 中文标签防乱码 user_na = TRUE # 把用户自定义缺失值转为 NA ) # 查看变量标签 lapply(data, function(x) attr(x, "label")) # 把 labelled 类型转为普通因子或数值,按需选择 data_clean <- data %>% mutate(across(where(is.labelled), ~ as_factor(.)))user_na = TRUE这个参数值得注意:SPSS 里可以把-9、-8等定义为“用户缺失”,haven默认不把它们转成NA,而是保留原值。如果不设这个参数,后面算均值时-9会被当成真实值参与计算,这就是典型的“血泪经验”——跑完描述统计发现均值是负的,回头查半天才发现缺失值没处理。as_factor会把 labelled 变量转成因子,适合做交叉表和卡方检验;如果要做回归,建议转成数值 dummy,而不是直接丢因子进去。
2.4 变量类型与测量尺度:读进来之后先做一次“体检”
不管用 Python 还是 R,读进来之后第一件事不是跑模型,而是做一次变量体检。重点看四类问题:第一,数值型变量是否被误读成字符串(常见于带千分位逗号或货币符号的字段);第二,日期时间变量是否被读成数字(SPSS 内部用秒数存储日期,读进来可能是1388534400这种值);第三,名义变量的编码是否一致(比如性别有的个案是1/2,有的是M/F);第四,缺失值定义是否被正确识别。我一般会跑一个简单的汇总表:
| 检查项 | Python 命令 | R 命令 | 异常信号 |
|---|---|---|---|
| 变量类型分布 | df.dtypes | sapply(data, class) | 数值列出现 object |
| 缺失值计数 | df.isna().sum() | colSums(is.na(data)) | 某列缺失占比超 30% |
| 唯一值数量 | df.nunique() | sapply(data, n_distinct) | 名义变量唯一值过多 |
| 值域范围 | df.describe() | summary(data) | 出现 -9、99 等编码缺失 |
这张表不是走形式,而是把“读进来”和“能分析”之间的差距量化。比如某列唯一值数量是 200,但变量标签写的是“省份”,那大概率是编码没对齐,需要回去查值标签映射。
3. 数据清洗与变量变换:把.sav变成能直接跑模型的表
3.1 缺失值处理:先区分“系统缺失”和“用户缺失”
SPSS 里的缺失分两种:系统缺失(空单元格)和用户缺失(用特定数值编码,如-9表示“拒答”、-8表示“不知道”)。读进 Python 或 R 后,系统缺失通常变成NaN或NA,但用户缺失仍是具体数值。常见做法是先把用户缺失映射成NaN,再决定填补策略。下面这段 Python 代码演示了从元数据里提取缺失定义并批量替换:
import numpy as np import pyreadstat df, meta = pyreadstat.read_sav("survey_data.sav", user_na=True) # meta.missing_ranges 是一个列表,每项对应一个变量的缺失值范围 for i, col in enumerate(meta.column_names): if i < len(meta.missing_ranges) and meta.missing_ranges[i]: for lo, hi in meta.missing_ranges[i]: # 把落在缺失范围内的值替换为 NaN df[col] = df[col].mask(df[col].between(lo, hi), np.nan) # 查看替换后各列缺失比例 missing_ratio = df.isna().mean().sort_values(ascending=False) print(missing_ratio[missing_ratio > 0])逻辑说明:user_na=True让pyreadstat把用户缺失信息放进meta.missing_ranges,而不是直接替换。然后手动按范围替换,这样可控性更强。参数lo, hi是缺失范围的上下界,SPSS 允许定义一个区间,比如-9到-1都算缺失。替换完之后看缺失比例,如果某列超过 40%,我一般会考虑直接剔除该变量,而不是硬填补,因为填补引入的偏差可能比丢掉这一列更大。
3.2 值标签与编码映射:什么时候套标签,什么时候保留编码
值标签是 SPSS 数据文件的一大优势,但套标签的时机很关键。做描述统计和交叉表时,套标签可读性更好;做回归、聚类、神经网络时,必须保留数值编码或做 one-hot。下面是一个按分析目标切换的示例:
# 场景一:做频数统计,套用值标签方便阅读 df_labeled, meta = pyreadstat.read_sav( "survey_data.sav", apply_value_formats=True ) print(df_labeled["gender"].value_counts()) # 场景二:做回归,保留原始编码并做 dummy df_raw, _ = pyreadstat.read_sav( "survey_data.sav", apply_value_formats=False ) dummies = pd.get_dummies(df_raw["gender"], prefix="gender", drop_first=True) df_model = pd.concat([df_raw.drop(columns=["gender"]), dummies], axis=1)参数drop_first=True是为了避免虚拟变量陷阱(完全共线性),在回归里是常规操作。如果做的是决策树或随机森林,可以保留全部 dummy,因为树模型对共线性不敏感。这里没有绝对对错,关键是知道自己在哪个场景下做选择。
3.3 日期时间与字符串字段的规范化
SPSS 的日期时间字段读进来往往是数值(从 1582-10-14 起的秒数),需要转换。字符串字段则可能带前后空格、全角半角混用、大小写不一致。下面这段处理逻辑我几乎每次都会跑一遍:
import pandas as pd # 日期转换:SPSS 秒数转 datetime if "interview_date" in df.columns: df["interview_date"] = pd.to_datetime( df["interview_date"], unit="s", origin="1582-10-14" ) # 字符串清洗:去空格、统一大小写、全角转半角 str_cols = df.select_dtypes(include="object").columns for col in str_cols: df[col] = ( df[col] .str.strip() .str.upper() .str.replace(" ", " ", regex=False) )origin="1582-10-14"是 SPSS 日期系统的起点,不写这个参数转换出来的日期会差几百年。字符串清洗里.str.upper()对中文没影响,但对混合了英文的字段有用;全角空格替换是中文数据里常见的坑,不处理的话groupby会把“北京”和“北京 ”当成两个组。
3.4 导出为可复现的中间格式
清洗完的数据建议存成.parquet或.feather,而不是 CSV。原因有两个:一是保留数据类型,避免下次读取时再次发生类型推断错误;二是读取速度快,尤其是数据量大的时候。下面是一段导出和回读的示例:
# 导出为 parquet,保留 dtype df.to_parquet("cleaned_data.parquet", index=False) # 回读时类型完全一致 df_reload = pd.read_parquet("cleaned_data.parquet") print(df_reload.dtypes)如果团队用 R,对应的是arrow::write_parquet()和arrow::read_parquet()。这一步看起来不起眼,但它是“可复现”的关键——下次跑分析不用再重复一遍清洗逻辑,直接读中间文件即可。
4. 避坑与排查:SPSS 数据文件处理中最容易翻车的五个点
4.1 中文乱码:现象是变量标签变成问号或方块
现象:用pandas.read_spss读进来后,meta.column_labels里的中文显示为???或乱码字符。原因:.sav文件在保存时使用的编码与读取时指定的编码不一致,常见于 Windows 中文环境保存的文件在 Linux 或 macOS 上读取。解决:显式指定encoding参数,pyreadstat支持UTF-8、GBK、GB18030等;如果仍乱码,用chardet检测文件编码后再读。我一般会先试GB18030,因为它兼容 GBK 和 GB2312,覆盖范围更广。
4.2 缺失值被当成真实值:均值算出负数
现象:某数值变量的均值明显不合理,比如年龄均值是-3.2。原因:SPSS 里用-9表示缺失,读取时没有设置user_na=True,导致-9参与计算。解决:读取时开启user_na,或读取后手动按meta.missing_ranges替换。这个坑的隐蔽性在于,如果不看描述统计的极值,很难发现。我现在的习惯是读完数据先跑df.describe(),看 min 和 max 是否在合理范围内。
4.3 值标签套用后无法做数值运算
现象:对性别变量做回归时报错TypeError: unsupported operand type。原因:读取时设了apply_value_formats=True,性别列变成字符串“男/女”,无法直接进入模型。解决:建模前保留原始编码,或对字符串列做 one-hot 编码。如果已经套了标签,可以用pd.Categorical重新编码回数值,但更稳妥的做法是读两份:一份套标签用于描述统计,一份保留编码用于建模。
4.4 日期字段读成整数:时间序列分析直接报错
现象:interview_date列的值是1388534400这种整数,做时间序列时无法按日期聚合。原因:SPSS 日期以秒数存储,读取时未转换。解决:用pd.to_datetime(unit="s", origin="1582-10-14")转换。注意origin参数不能省,否则日期会偏移。如果数据里混了缺失日期(如-9),先替换成NaT再转换,否则会抛异常。
4.5 变量名重复或非法:merge 时产生笛卡尔积
现象:两个数据框 merge 后行数暴增。原因:.sav文件里允许变量名重复(虽然不常见),或者变量名包含空格、特殊字符,读进 pandas 后被自动重命名,导致 merge key 对不上。解决:读取后先检查df.columns.duplicated().any(),如果有重复列名,手动重命名;merge 前用df.columns确认 key 列名一致。我一般会在 merge 后加一句assert len(merged) == len(df_left),行数不对就立刻停下来查。
5. 进阶技巧:用pyreadstat的元数据做自动化变量字典
5.1 从meta对象生成变量字典表
pyreadstat返回的meta对象里藏着完整的变量字典信息,包括变量名、标签、值标签、测量尺度、缺失定义、格式。把这些信息导出成一张表,可以作为数据治理的起点。下面这段代码生成一个变量字典 DataFrame:
import pandas as pd import pyreadstat df, meta = pyreadstat.read_sav("survey_data.sav", user_na=True) var_dict = pd.DataFrame({ "var_name": meta.column_names, "var_label": meta.column_labels, "var_type": meta.original_variable_types, "measure": meta.variable_measure, "value_labels": [meta.variable_value_labels.get(c, {}) for c in meta.column_names], "missing_ranges": meta.missing_ranges, }) # 导出为 Excel,方便团队共享 var_dict.to_excel("variable_dictionary.xlsx", index=False)逻辑说明:meta.variable_measure返回的是nominal、ordinal、scale三种尺度,对应 SPSS 里的名义、有序、标度。meta.variable_value_labels是一个字典,key 是变量名,value 是{编码: 标签}的映射。missing_ranges是列表,每项对应一个变量的缺失范围。把这些拼成一张表,团队里不碰 SPSS 的人也能看懂每个字段的含义。这一步看起来是额外工作,但下次有人问“这个Q12变量是什么意思”时,直接甩这张表就行,省掉大量沟通成本。
5.2 用变量字典驱动自动化清洗
有了变量字典,清洗逻辑可以按测量尺度自动分流:标度变量做缺失值替换和标准化,名义变量做 one-hot,有序变量做序数编码。下面是一个按尺度分流的示例:
from sklearn.preprocessing import StandardScaler # 按测量尺度分组 scale_cols = var_dict[var_dict["measure"] == "scale"]["var_name"].tolist() nominal_cols = var_dict[var_dict["measure"] == "nominal"]["var_name"].tolist() ordinal_cols = var_dict[var_dict["measure"] == "ordinal"]["var_name"].tolist() # 标度变量:缺失值用中位数填补,再做标准化 for col in scale_cols: if col in df.columns: df[col] = df[col].fillna(df[col].median()) df[scale_cols] = StandardScaler().fit_transform(df[scale_cols]) # 名义变量:one-hot df = pd.get_dummies(df, columns=nominal_cols, drop_first=True) # 有序变量:按值标签顺序做序数编码 for col in ordinal_cols: labels = meta.variable_value_labels.get(col, {}) if labels: order = [k for k, v in sorted(labels.items(), key=lambda x: x[0])] df[col] = pd.Categorical(df[col], categories=order, ordered=True).codes这段代码的价值在于“可复用”:换一份.sav文件,只要变量字典结构一致,清洗逻辑不用重写。参数drop_first=True在 one-hot 时避免共线性;有序变量的sorted按编码排序,如果值标签的顺序与编码顺序不一致,需要手动指定categories顺序。我一般会把这段逻辑封装成一个函数,输入df和meta,输出清洗后的df,下次直接调用。
5.3 验证清洗结果:三个必查指标
清洗完不要直接跑模型,先做一次验证。我一般查三个指标:第一,缺失值比例是否降到可接受范围(通常低于 5%);第二,数值变量的取值范围是否合理(用describe()看 min/max);第三,one-hot 后的列数是否与预期一致(名义变量 k 个类别应产生 k-1 列)。如果这三个指标都正常,再进入建模环节。从那以后我每次处理新的.sav文件,都强制走一遍“读元数据 → 生成变量字典 → 按尺度分流清洗 → 验证三个指标”的流程,不再凭感觉点菜单。希望帮到你。
本文还有配套的精品资源,点击获取