跳转到内容

序列数据重塑:宽格式与长格式

很多做社会序列分析的工具,只“认”宽格式的数据。

👉 啥是宽格式?

打个比方,就像给每个人做一张“时间记录表”:
一个人占一行,每一列对应一个时间点(比如 2000 年、2001 年),一眼就能看到某个人所有时间点的状态。

❌ 宽格式的“不趁手”场景

真要干活的时候,宽格式就有点容易掉链子:

  • 想画个趋势图?(比如看 A 和 B 两个人 2000-2002 年的状态变化)
    • 绘图工具(比如 matplotlibseaborn)大多只认“一行一个时间点”的数据,宽格式的列太多,工具根本不知道“哪个是时间、哪个是状态”,画不出图;
  • 想把这份数据和其他表合并?(比如合并“每个人的年龄信息”)
    • 宽格式里一个人占一行,看似方便,但如果其他表是“一行一个时间点”的格式,合并时很容易出错,得手动调整半天;
  • 想做统计建模?(比如分析“时间和状态的关系”)
    • 模型需要“每个观测值占一行”—— 宽格式里一个人一行包含多个时间点的观测,模型读不懂,会直接报错。

✅ 长格式:解决痛点的“适配款”

这时候长格式就派上用场了:

  • 核心规则:一个个体的一个时间点,占一行;
  • 固定列数:不管多少数据,都只有 个体 ID时间状态 三列;
  • 工具友好:绘图、合并、建模时,工具能直接“抓”到关键信息,不用你手动拆数据、改格式。

📌 关键原则:没有“更好”,只有“更适配”

没有说哪种格式更好,关键看你要做啥:

  • 用宽格式:看数据、整理原始信息(直观);
  • 用长格式:干活(绘图、建模);
  • 后续要做序列分析,再转回宽格式就行。

🎯 这篇指南能帮你啥?

不用手动复制粘贴改格式,用两个现成的辅助函数,点几下就能在宽、长格式之间切换,省时间还不容易出错。


宽格式

宽格式中,每一行代表一个个体,每一列代表一个时间点

python
# 宽格式示例
# 导入pandas库(处理表格数据的常用工具)
import pandas as pd

df = pd.DataFrame({
    "Entity ID": ["A", "B"],    # 每个个体的唯一标识(比如人、样本的编号)
    "2000": ["M", "L"],         # 2000年对应的状态(比如等级、类别)
    "2001": ["H", "M"],         # 2001年对应的状态
    "2002": ["H", "H"]          # 2002年对应的状态
})

宽格式数据展示:

Entity ID200020012002
AMHH
BLMH

转换为长格式

wide_to_long_format_data()函数:

python
# 从sequenzo工具库导入转换函数
from sequenzo.utils import wide_to_long_format_data

df_long = wide_to_long_format_data(
    df,                                 # 输入的宽格式数据表(就是上面创建的df)
    id_col="Entity ID",                 # 个体标识列的名称(这里是“个体ID”)
    time_cols=["2000", "2001", "2002"], # 代表时间点的列名(要转成时间列的那些列)
    var_name="year",                    # 新生成的“时间列”叫什么名字(这里设为“year”,即年份)
    value_name="state"                  # 新生成的“状态列”叫什么名字(这里设为“state”,即状态)
)

转换后的长格式数据展示:

Entity IDyearstate
A2000M
A2001H
A2002H
B2000L
B2001M
B2002H
  • id_col:个体标识列(比如示例中的 "Entity ID"),作用是区分不同的分析对象(比如 AB 是两个不同的个体)。
  • time_cols:要转成“时间”的列名列表(比如示例中的 ["2000", "2001", "2002"]),这些列原本是宽格式里的时间点。
  • var_name:新生成的时间列的名称(默认叫 "time" ),比如数据是按年份记录的,就可以设为 "year"
  • value_name:新生成的状态列的名称(默认叫 "state"),用来存每个时间点对应的具体状态(比如等级、类别)。

转回宽格式

如果后续需要用宽格式数据,用 long_to_wide_format_data() 函数就能转回去:

python
# 从sequenzo工具库导入转换函数
from sequenzo.utils import long_to_wide_format_data

df_wide = long_to_wide_format_data(
    df_long,                # 输入的长格式DataFrame
    id_col="Entity ID",     # 个体标识列的名称
    time_col="year",        # 时间列的名称(对应长格式中的时间列)
    state_col="state"       # 状态值列的名称(对应长格式中的状态列)
)

运行后会得到和原始数据一样的宽格式 DataFrame

当然!这里有针对 var_namevalue_name 两个参数在上下文中的英文解释(宽转长格式转换场景下):

深入理解长宽格式转化中的 var_namevalue_name 参数

pandas.melt() 或者 wide_to_long_format_data() 这两个函数,把序列数据从宽格式改成长格式时,var_namevalue_name 这两个参数用来决定长格式结果里“新增列叫什么名字”。

var_name="time":给“时间列”起名字

这个参数设置“新时间列”的名称,这一列会存放原来宽格式中的列名(通常是时间跨度,比如年份、年龄等)。

举个例子:

plaintext
# 宽格式输入
ID     2000    2001    2002
A      M       H       H
B      L       M       H

转为长格式后:

plaintext
ID     time    state
A      2000    M
A      2001    H
A      2002    H
B      2000    L
B      2001    M
B      2002    H

这里的 "time" 就是新的时间列的名称,里面存的是原来的列名 200020012002。你可以根据数据特点改成更贴切的名字,比如 "year"(年份)或 "age"(年龄)。


value_name="state"

这个参数用来设定新状态列的名称。这一列里存的内容,是原来宽格式中“单元格里的实际值”(也就是每个时间点对应的状态)。
比如你的数据记录的是 "婚姻状态",就可以把 value_name 设为 "marital_status",这样一看列名就知道存的是什么,不用猜:

举个例子:

要是你的数据记的是婚姻状态(marital status)随时间变化的情况,你可以这么弄:

python
df_long = wide_to_long_format_data(
    df,
    id_col="person_id",                  # 个体标识列(这里叫"person_id",即人员 ID )
    time_cols=["2000", "2001", "2002"],  # 时间列(年份)
    var_name="year",                     # 时间列名设为"year"(年份)
    value_name="marital_status"          # 状态列名设为"marital_status"(婚姻状态)
)

这样就会得到:

plaintext
person_id   year    marital_status
A           2000    Single
A           2001    Married
A           2002    Married
B           2000    Single
...

总结

参数名称意义(存什么、叫什么)常见取值(根据数据选)
var_name时间列的名称(数据来自原宽格式里“代表时间的列名”,比如 20002001"time"(时间)、"year"(年份)、"age"(年龄)
value_name状态列的名称(数据来自原宽格式里“单元格里的实际值”,比如 MH"state"(状态)、"marital_status"(婚姻状态)、"grade"(等级)

这两个参数仅用于设定长格式中列的名称,没有固定要求,只要符合你的研究场景、能让自己和别人看懂就行。

作者:梁彧祺
翻译:杨子婷

Released under the BSD-3-Clause License.