23、数据整理与重塑:多对多关系处理及宽长格式转换

数据整理与重塑:多对多关系处理及宽长格式转换

在数据处理过程中,我们常常会遇到各种复杂的数据结构和格式问题,比如多对多关系的数据重复,以及变量值嵌入列名导致的数据不整洁。本文将详细介绍如何处理这些问题,包括修复多对多关系、使用 stack melt 将数据从宽格式转换为长格式、使用 wide_to_long 一次性处理多组列的转换,以及使用 unstack pivot 将数据从长格式转换回宽格式。

1. 修复多对多关系

在处理博物馆藏品相关数据时,可能会遇到包含藏品、引用和创作者信息的数据集,其中存在多对多关系,导致数据重复。以下是处理这种情况的步骤:

1.1 合并数据并填充缺失值

import pandas as pd
# 假设 cmacollections 和 youngartists 是已有的 DataFrame
cmacollections = pd.merge(cmacollections, youngartists, left_on=['id'], right_on=['id'], how='left')
cmacollections.creatorbornafter1950.fillna("N", inplace=True)
print(cmacollections.shape)
print(cmacollections.creatorbornafter1950.value_counts())

1.2 分析数据

此时有三个 DataFrame cmacollections cmacitations cmacreators cmacollections cmacitations cmacreators 均为一对多关系。数据中存在 972 个唯一的 id 值,表明可能只有 972 个藏品。有 9,758 个唯一的 id 和引用对,平均每个藏品约有 10 个引用;有 1,055 个 id 和创作者对。

1.3 处理数据重复问题

id 列为指导,将数据整理成更合适的形状:
- 创建 collections DataFrame

# 假设 cmacollections 是已有的 DataFrame
collections = cmacollections.reset_index().drop_duplicates(subset='id').set_index('id')[['title', 'collection', 'type']]
  • 创建 citations creators DataFrame
# 假设 cmacitations 和 cmacreators 是已有的 DataFrame
citations = cmacitations.drop_duplicates(subset=['id', 'citation'])
creators = cmacreators.drop_duplicates(subset=['id', 'creator'])

1.4 进行数据分析

例如,计算至少有一位 1950 年后出生创作者的藏品数量:

# 假设 youngartists 是已有的 DataFrame
youngartists.shape[0] == youngartists.index.nunique()

1.5 多对多合并的问题及解决

多对多合并导致的数据重复在处理定量数据时最为棘手。例如,如果藏品有评估价值,该值会像标题一样重复,生成的描述性统计数据会有偏差。因此,规范化和整洁的数据非常重要,应将评估价值列包含在 cmacollections DataFrame 中。

2. 使用 stack melt 将数据从宽格式转换为长格式

2.1 问题背景

在分析或调查数据中,常出现变量值嵌入列名的情况,如美国国家纵向青年调查(NLS)数据,每个受访者每年的工作周数和大学入学状态数据存储在不同列中。

2.2 准备数据

import pandas as pd
nls97 = pd.read_csv("data/nls97f.csv")
nls97.set_index(['originalid'], inplace=True)
weeksworkedcols = ['weeksworked00', 'weeksworked01', 'weeksworked02', 'weeksworked03', 'weeksworked04']
print(nls97[weeksworkedcols].head(2).T)
print(nls97.shape)

2.3 使用 stack 转换数据

weeksworked = nls97[weeksworkedcols].stack(dropna=False).reset_index().rename(columns={'level_1': 'year', 0: 'weeksworked'})
weeksworked['year'] = weeksworked.year.str[-2:].astype(int) + 2000
print(weeksworked.head(10))
print(weeksworked.shape)

2.4 使用 melt 转换数据

weeksworked = nls97.reset_index().loc[:, ['originalid'] + weeksworkedcols].melt(id_vars=['originalid'], value_vars=weeksworkedcols, var_name='year', value_name='weeksworked')
weeksworked['year'] = weeksworked.year.str[-2:].astype(int) + 2000
weeksworked.set_index(['originalid'], inplace=True)
print(weeksworked.loc[[8245, 3962]])

2.5 转换大学入学状态列

colenrcols = ['colenroct00', 'colenroct01', 'colenroct02', 'colenroct03', 'colenroct04']
colenr = nls97.reset_index().loc[:, ['originalid'] + colenrcols].melt(id_vars=['originalid'], value_vars=colenrcols, var_name='year', value_name='colenr')
colenr['year'] = colenr.year.str[-2:].astype(int) + 2000
colenr.set_index(['originalid'], inplace=True)
print(colenr.loc[[8245, 3962]])

2.6 合并数据

workschool = pd.merge(weeksworked, colenr, on=['originalid', 'year'], how="inner")
print(workschool.shape)
print(workschool.loc[[8245, 3962]])

2.7 原理分析

stack 会将所有列名移到索引中,而 melt 提供了更多灵活性,可以基于非索引的 ID 变量旋转列名和值。

3. 使用 wide_to_long 一次性处理多组列的转换

3.1 准备数据

import pandas as pd
nls97 = pd.read_csv("data/nls97f.csv")
nls97.set_index('personid', inplace=True)
weeksworkedcols = ['weeksworked00', 'weeksworked01', 'weeksworked02', 'weeksworked03', 'weeksworked04']
colenrcols = ['colenroct00', 'colenroct01', 'colenroct02', 'colenroct03', 'colenroct04']
print(nls97.loc[nls97.originalid.isin([1, 2]), ['originalid'] + weeksworkedcols + colenrcols].T)

3.2 运行 wide_to_long 函数

workschool = pd.wide_to_long(nls97[['originalid'] + weeksworkedcols + colenrcols], stubnames=['weeksworked', 'colenroct'], i=['originalid'], j='year').reset_index()
workschool['year'] = workschool.year + 2000
workschool = workschool.sort_values(['originalid', 'year'])
workschool.set_index(['originalid'], inplace=True)
print(workschool.head(10))

3.3 原理分析

wide_to_long 函数需要提供列组的字符,它会将后缀转换为有意义的值并将其融合到以 j 参数命名的列中。

3.4 不同后缀情况

当列组的后缀不同时,某些列组对应后缀的值会缺失。例如,排除 weeksworked03 并添加 weeksworked05

weeksworkedcols = ['weeksworked00', 'weeksworked01', 'weeksworked02', 'weeksworked04', 'weeksworked05']
workschool = pd.wide_to_long(nls97[['originalid'] + weeksworkedcols + colenrcols], stubnames=['weeksworked', 'colenroct'], i=['originalid'], j='year').reset_index()
workschool['year'] = workschool.year + 2000
workschool = workschool.sort_values(['originalid', 'year'])
workschool.set_index(['originalid'], inplace=True)
print(workschool.head(12))

4. 使用 unstack pivot 将数据从长格式转换为宽格式

4.1 准备数据

import pandas as pd
nls97 = pd.read_csv("data/nls97f.csv")
nls97.set_index(['originalid'], inplace=True)
weeksworkedcols = ['weeksworked00', 'weeksworked01', 'weeksworked02', 'weeksworked03', 'weeksworked04']

4.2 重复堆叠和融合操作

# 堆叠数据
weeksworkedstacked = nls97[weeksworkedcols].stack(dropna=False)
print(weeksworkedstacked.loc[[1, 2]])
# 融合数据
weeksworkedmelted = nls97.reset_index().loc[:, ['originalid'] + weeksworkedcols].melt(id_vars=['originalid'], value_vars=weeksworkedcols, var_name='year', value_name='weeksworked')
print(weeksworkedmelted.loc[weeksworkedmelted.originalid.isin([1, 2])].sort_values(['originalid', 'year']))

4.3 使用 unstack 转换数据

weeksworked = weeksworkedstacked.unstack()
print(weeksworked.loc[[1, 2]])

4.4 使用 pivot 转换数据

weeksworked = weeksworkedmelted.pivot(index='originalid', columns='year', values=['weeksworked']).reset_index()
weeksworked.columns = ['originalid'] + [col[1] for col in weeksworked.columns[1:]]
print(weeksworked.loc[weeksworked.originalid.isin([1, 2])].T)

4.5 原理分析

unstack 使用 stack 创建的多级索引来旋转数据, pivot 需要指定索引列、用于列名后缀的列和要解融合的值所在的列。 pivot 会返回多级列名,可通过 [col[1] for col in weeksworked.columns[1:]] 进行处理。

总结

通过以上方法,我们可以有效地处理多对多关系的数据重复问题,以及宽长格式数据的转换问题。在实际应用中,应根据数据的特点和需求选择合适的方法,以确保数据的整洁和分析的准确性。

下面是一个简单的流程图,展示了数据从宽格式转换为长格式的主要步骤:

graph LR
    A[宽格式数据] --> B[选择列]
    B --> C{使用 stack 或 melt}
    C -->|stack| D[移动列名到索引]
    C -->|melt| E[基于 ID 变量旋转列名和值]
    D --> F[重置索引并重命名列]
    E --> F
    F --> G[处理年份值]
    G --> H[长格式数据]

同时,为了更清晰地展示不同方法的特点,我们可以列出一个表格:
| 方法 | 适用场景 | 优点 | 缺点 |
| ---- | ---- | ---- | ---- |
| stack | 简单的列名到索引的转换 | 操作简单 | 灵活性较差,会将所有列名移到索引中 |
| melt | 需要基于非索引的 ID 变量转换 | 灵活性高 | 代码相对复杂 |
| wide_to_long | 一次性处理多组列的转换 | 功能强大 | 设置较复杂 |
| unstack | 将堆叠的数据转换回宽格式 | 利用多级索引,操作相对简单 | 依赖于 stack 创建的多级索引 |
| pivot | 将融合的数据转换回宽格式 | 可指定多个参数进行转换 | 会返回多级列名,需要额外处理 |

5. 不同数据转换方法的对比与选择

5.1 转换效率对比

在处理大规模数据时,不同转换方法的效率差异会更加明显。以下是一个简单的对比表格,展示了不同方法在处理数据时的大致效率情况:
| 方法 | 数据规模适应性 | 转换速度 | 内存占用 |
| ---- | ---- | ---- | ---- |
| stack | 小规模数据 | 快 | 低 |
| melt | 中等规模数据 | 适中 | 适中 |
| wide_to_long | 大规模数据 | 快 | 高 |
| unstack | 小规模数据 | 快 | 低 |
| pivot | 中等规模数据 | 适中 | 适中 |

5.2 选择合适的方法

在实际应用中,需要根据数据的特点和需求来选择合适的转换方法。以下是一个选择流程图:

graph LR
    A[数据转换需求] --> B{数据规模大小}
    B -->|小规模| C{是否简单列名转换}
    C -->|是| D[使用 stack 或 unstack]
    C -->|否| E{是否基于非索引 ID 变量}
    E -->|是| F[使用 melt 或 pivot]
    E -->|否| G[考虑其他方法]
    B -->|中等规模| E
    B -->|大规模| H{是否多组列转换}
    H -->|是| I[使用 wide_to_long]
    H -->|否| E

5.3 示例分析

假设我们有一个包含大量受访者多年工作周数和大学入学状态的数据集,数据规模较大且需要一次性处理多组列的转换,那么 wide_to_long 方法可能是最合适的选择。以下是一个示例代码:

import pandas as pd

# 加载数据
nls97 = pd.read_csv("data/nls97f.csv")
nls97.set_index('personid', inplace=True)

# 定义列名
weeksworkedcols = ['weeksworked00', 'weeksworked01', 'weeksworked02', 'weeksworked03', 'weeksworked04']
colenrcols = ['colenroct00', 'colenroct01', 'colenroct02', 'colenroct03', 'colenroct04']

# 使用 wide_to_long 进行转换
workschool = pd.wide_to_long(nls97[['originalid'] + weeksworkedcols + colenrcols], stubnames=['weeksworked', 'colenroct'], i=['originalid'], j='year').reset_index()
workschool['year'] = workschool.year + 2000
workschool = workschool.sort_values(['originalid', 'year'])
workschool.set_index(['originalid'], inplace=True)

print(workschool.head(10))

6. 实际应用案例

6.1 市场调研数据处理

在市场调研中,我们可能会收集到关于消费者在不同时间段的购买行为和满意度的数据。这些数据通常以宽格式存储,例如每个消费者的不同月份的购买金额和满意度评分分别存储在不同列中。我们可以使用 melt 方法将其转换为长格式,以便进行更方便的分析。以下是一个示例代码:

import pandas as pd

# 模拟市场调研数据
data = {
    'consumer_id': [1, 2, 3],
    'purchase_jan': [100, 200, 300],
    'purchase_feb': [150, 250, 350],
    'satisfaction_jan': [4, 5, 3],
    'satisfaction_feb': [3, 4, 5]
}

df = pd.DataFrame(data)

# 使用 melt 转换数据
purchase_cols = [col for col in df.columns if 'purchase' in col]
satisfaction_cols = [col for col in df.columns if 'satisfaction' in col]

purchase_melted = df.melt(id_vars=['consumer_id'], value_vars=purchase_cols, var_name='month', value_name='purchase_amount')
satisfaction_melted = df.melt(id_vars=['consumer_id'], value_vars=satisfaction_cols, var_name='month', value_name='satisfaction_score')

# 合并数据
merged = pd.merge(purchase_melted, satisfaction_melted, on=['consumer_id', 'month'])

print(merged)

6.2 医疗数据处理

在医疗领域,患者的多次检查结果可能以宽格式存储,例如不同年份的血压、血糖等指标分别存储在不同列中。我们可以使用 wide_to_long 方法一次性将多组列转换为长格式,以便进行趋势分析。以下是一个示例代码:

import pandas as pd

# 模拟医疗数据
data = {
    'patient_id': [1, 2, 3],
    'blood_pressure_2020': [120, 130, 140],
    'blood_pressure_2021': [122, 132, 142],
    'blood_sugar_2020': [90, 95, 100],
    'blood_sugar_2021': [92, 97, 102]
}

df = pd.DataFrame(data)

# 使用 wide_to_long 转换数据
workschool = pd.wide_to_long(df, stubnames=['blood_pressure', 'blood_sugar'], i=['patient_id'], j='year').reset_index()
workschool['year'] = workschool.year + 2020

print(workschool)

7. 总结与建议

7.1 总结

本文详细介绍了处理多对多关系的数据重复问题以及宽长格式数据转换的方法,包括 stack melt wide_to_long unstack pivot 。通过示例代码和原理分析,我们了解了不同方法的适用场景、优缺点和操作步骤。同时,通过实际应用案例展示了这些方法在市场调研和医疗数据处理中的应用。

7.2 建议

  • 在处理数据时,首先要明确数据的特点和需求,选择合适的转换方法。
  • 对于小规模数据,可以优先考虑 stack unstack 方法,操作简单且效率较高。
  • 对于中等规模数据, melt pivot 方法具有较高的灵活性,可以根据具体情况选择。
  • 对于大规模数据, wide_to_long 方法功能强大,但设置较复杂,需要仔细配置参数。
  • 在实际应用中,可以结合使用不同的方法,以达到最佳的数据处理效果。

最后,为了方便大家回顾不同方法的操作步骤,我们列出一个简单的列表:
1. stack 方法 :选择列 -> 移动列名到索引 -> 重置索引并重命名列。
2. melt 方法 :选择列 -> 基于 ID 变量旋转列名和值 -> 处理年份值。
3. wide_to_long 方法 :选择列 -> 运行 wide_to_long 函数 -> 处理年份值。
4. unstack 方法 :使用 stack 创建的多级索引旋转数据。
5. pivot 方法 :指定索引列、列名后缀列和值列 -> 处理多级列名。

通过这些方法和建议,希望大家能够更加高效地处理数据,提高数据分析的准确性和效率。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值