长宽数据转换——Python VS R

序言

“人生苦短,我用Python”,这句Python界鼎鼎大名的话,一直被我奉为经典,因而我也一直坚持使用Python作为我的主力编程语言。

无奈公司项目上统计建模用的都是R,还好有一点儿小基础,学习起来也不是很难。

但是一直还是想把公司的一整套代码都用Python重新写一遍。

因此在通读并深入了解公司项目建模R代码的时候,也想记录下一些R函数,然后用Python实现一遍,留作之后用Python实现建模的参考依据。

需求

处理数据的时候经常需要进行数据的合并和拆分,即长宽数据之间的转换

比如,在数据库里,由于每个财务指标都会有各自的属性,那在
储存发债主体的财务数据的时候,就不会按下面格式进行储存(数据瞎找的):

发债主体 流动资产 非流动资产 总资产 ……
上海大众公用事业(集团)股份有限公司 6,136,627012 16,348,531461 22,485,158474 ……

上面这种格式的数据称为宽数据

而更有可能会这样储存:

发债主体 财务指标 数值
上海大众公用事业(集团)股份有限公司 流动资产 6,136,627,012
上海大众公用事业(集团)股份有限公司 非流动资产 16,348,531,461
上海大众公用事业(集团)股份有限公司 总资产 22,485,158,474
…… …… ……

上面这种格式的数据称为长数据

而在建模、数据分析的时候,这两种数据格式都有可能用到,所以需要掌握两种数据格式之间的互相转换。

为了方便记录,我引用九月哥的数据,作为例子:

Name Company Sale2013 Sale2014 Sale2015 Sale2016
苹果 Apple 5000 5050 5050 5050
谷歌 Google 3500 3800 3800 3800
脸书 Facebook 2300 2900 2900 2900
亚马逊 Amazon 2100 2500 2500 2500
腾讯 Tencent 3100 3300 3300 3300

R实现长宽数据转换

R实现长宽数据转换的包有两个:

  1. reshape2::melt/dcast
  2. tidyr::gather/spread
    这两个包的函数逻辑有所不同。

数据如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if(! "tidyr" %in% installed.packages()) install.packages("tidyr")
library(tidyr)
if(! "reshape2" %in% installed.packages()) install.packages("reshape2")
library(reshape2)

data<-data.frame(
       Name = c("苹果","谷歌","脸书","亚马逊","腾讯"),
       Company = c("Apple","Google","Facebook","Amozon","Tencent"),
       Sale2013 = c(5000,3500,2300,2100,3100),
       Sale2014 = c(5050,3800,2900,2500,3300),
       Sale2015 = c(5050,3800,2900,2500,3300),
       Sale2016 = c(5050,3800,2900,2500,3300)
)
data
A data.frame: 5 × 6
NameCompanySale2013Sale2014Sale2015Sale2016
<fct><fct><dbl><dbl><dbl><dbl>
苹果 Apple 5000505050505050
谷歌 Google 3500380038003800
脸书 Facebook2300290029002900
亚马逊Amozon 2100250025002500
腾讯 Tencent 3100330033003300

reshape2包实现

1
2
3
4
5
6
7
8
9
# 宽数据转长数据
# 即:数据融合
long_data <- melt(
    data, # 长数据
    id.vars = c("Name", "Company"), # 需要保留的字段
    variable.name = "Year", # 转换后的维度
    value.name = "Sale" # 转换后的变量名
)
long_data
A data.frame: 20 × 4
NameCompanyYearSale
<fct><fct><fct><dbl>
苹果 Apple Sale20135000
谷歌 Google Sale20133500
脸书 FacebookSale20132300
亚马逊Amozon Sale20132100
腾讯 Tencent Sale20133100
苹果 Apple Sale20145050
谷歌 Google Sale20143800
脸书 FacebookSale20142900
亚马逊Amozon Sale20142500
腾讯 Tencent Sale20143300
苹果 Apple Sale20155050
谷歌 Google Sale20153800
脸书 FacebookSale20152900
亚马逊Amozon Sale20152500
腾讯 Tencent Sale20153300
苹果 Apple Sale20165050
谷歌 Google Sale20163800
脸书 FacebookSale20162900
亚马逊Amozon Sale20162500
腾讯 Tencent Sale20163300

id.vars是识别变量的key,如果一个主体有很多个共用的属性(比如这里的公司中文名和公司英文名),
可以都写进去。

宽数据转换成长数据后,会多出一列变量用来储存原来的列名,variable.name给这个列名组成的列起了一个新的列名。
value.name同理。

1
2
3
4
5
6
7
8
# 长数据转宽数据
# 即数据拆分、数据透视
short_data <- dcast(
    long_data, # 待转换数据
    Name + Company ~ Year, # 转换公式,~左边是公共属性,不被扩宽;~右边的是转化的维度
    value.var = "Sale" # 转换后的数值,如果只有一列,则可以省略,会自动overwrite
)
short_data
A data.frame: 5 × 6
NameCompanySale2013Sale2014Sale2015Sale2016
<fct><fct><dbl><dbl><dbl><dbl>
亚马逊Amozon 2100250025002500
脸书 Facebook2300290029002900
腾讯 Tencent 3100330033003300
苹果 Apple 5000505050505050
谷歌 Google 3500380038003800

tidyr包实现

tidry包中对应reshape2::melt的是gather,对应reshape2::dcast的是spread,上面两个例子可以这么实现:

1
2
3
4
5
6
7
long_data_2 <- gather(
    data = data,
    key = "Year",
    value = "Sale",
    Sale2013:Sale2016  # 指定要融合的列,也可以用-XX的形式表示除了XX列外
)
long_data_2
A data.frame: 20 × 4
NameCompanyYearSale
<fct><fct><chr><dbl>
苹果 Apple Sale20135000
谷歌 Google Sale20133500
脸书 FacebookSale20132300
亚马逊Amozon Sale20132100
腾讯 Tencent Sale20133100
苹果 Apple Sale20145050
谷歌 Google Sale20143800
脸书 FacebookSale20142900
亚马逊Amozon Sale20142500
腾讯 Tencent Sale20143300
苹果 Apple Sale20155050
谷歌 Google Sale20153800
脸书 FacebookSale20152900
亚马逊Amozon Sale20152500
腾讯 Tencent Sale20153300
苹果 Apple Sale20165050
谷歌 Google Sale20163800
脸书 FacebookSale20162900
亚马逊Amozon Sale20162500
腾讯 Tencent Sale20163300
1
2
3
4
5
6
short_data_2 <- spread(
    data = long_data_2,
    key = Year,
    value = Sale
)
short_data_2
A data.frame: 5 × 6
NameCompanySale2013Sale2014Sale2015Sale2016
<fct><fct><dbl><dbl><dbl><dbl>
亚马逊Amozon 2100250025002500
脸书 Facebook2300290029002900
腾讯 Tencent 3100330033003300
苹果 Apple 5000505050505050
谷歌 Google 3500380038003800

reshape2和tidyr这几个函数的逻辑是不一样的,melt和dcast围绕id,即在数据中不变的那部分去做转换;
而gather和spread则只看datakeyvalue,直观上更容易。

Python实现长宽数据转换

Python的实现主要靠dataframe自带的melt实现数据融合,pivot_table即数据透视表实现数据拆分。

1
2
import pandas as pd
import numpy as np
1
2
3
4
5
6
7
8
data=pd.DataFrame({
    "Name":["苹果","谷歌","脸书","亚马逊","腾讯"],
    "Company":["Apple","Google","Facebook","Amozon","Tencent"],
    "Sale2013":[5000,3500,2300,2100,3100],
    "Sale2014":[5050,3800,2900,2500,3300],
    "Sale2015":[5050,3800,2900,2500,3300],
    "Sale2016":[5050,3800,2900,2500,3300]
})
1
2
3
4
5
6
7
# 宽数据转长数据
long_data = data.melt(
    id_vars=["Name","Company"], #要保留的主字段
    var_name="Year", #拉长的分类变量
    value_name="Sale" #拉长的度量值名称
)
long_data
Name Company Year Sale
0 苹果 Apple Sale2013 5000
1 谷歌 Google Sale2013 3500
2 脸书 Facebook Sale2013 2300
3 亚马逊 Amozon Sale2013 2100
4 腾讯 Tencent Sale2013 3100
5 苹果 Apple Sale2014 5050
6 谷歌 Google Sale2014 3800
7 脸书 Facebook Sale2014 2900
8 亚马逊 Amozon Sale2014 2500
9 腾讯 Tencent Sale2014 3300
10 苹果 Apple Sale2015 5050
11 谷歌 Google Sale2015 3800
12 脸书 Facebook Sale2015 2900
13 亚马逊 Amozon Sale2015 2500
14 腾讯 Tencent Sale2015 3300
15 苹果 Apple Sale2016 5050
16 谷歌 Google Sale2016 3800
17 脸书 Facebook Sale2016 2900
18 亚马逊 Amozon Sale2016 2500
19 腾讯 Tencent Sale2016 3300
1
2
3
4
5
6
7
# 长数据转宽数据
short_data = long_data.pivot_table(
    index=["Name","Company"], #行索引(可以使多个类别变量)
    columns=["Year"], #列索引(可以使多个类别变量)
    values=["Sale"] #值(一般是度量指标)
)
short_data
Sale
Year Sale2013 Sale2014 Sale2015 Sale2016
Name Company
亚马逊 Amozon 2100 2500 2500 2500
脸书 Facebook 2300 2900 2900 2900
腾讯 Tencent 3100 3300 3300 3300
苹果 Apple 5000 5050 5050 5050
谷歌 Google 3500 3800 3800 3800

参考链接

  1. 如何用R来处理数据表的长宽转换(图文详解)
  2. reshape2 数据操作 数据融合(cast)
  3. R之data.table -melt/dcast(数据拆分和合并)