はじめに

deepblueインターン生の中山です。
【列追加、重複削除、列名変更、結合、マージ、集約】PythonとRのデータフレーム操作比較 vol.2
の続きです。
集計、ソート、縦持ち・横持ち変換について、tidyverseを用いたRとPythonの比較をしたいと思います。

連載

使ったデータ

使ったデータは、MineThatData E-Mail Analytics And Data Mining Challenge datasetです。
前回のblogとデータは同じです。本記事では、history3000以上に条件付けている事が多いですが、データ数を減らして確認しやすくしているだけで、深い意味はないです。

データ読み込み

csvデータを読み込むコード。
tailで末尾のデータを確認しています。

Python

import pandas as pd
PATH = "http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv"
df = pd.read_csv(PATH)
df.tail()

#        recency history_segment  history  mens  womens   zip_code  newbie       channel        segment  visit  conversion  spend
# 63995       10  2) $100 - $200   105.54     1       0      Urban       0           Web    Mens E-Mail      0           0    0.0
# 63996        5    1) $0 - $100    38.91     0       1      Urban       1         Phone    Mens E-Mail      0           0    0.0
# 63997        6    1) $0 - $100    29.99     1       0      Urban       1         Phone    Mens E-Mail      0           0    0.0
# 63998        1  5) $500 - $750   552.94     1       0  Surburban       1  Multichannel  Womens E-Mail      0           0    0.0
# 63999        1  4) $350 - $500   472.82     0       1  Surburban       0           Web    Mens E-Mail      0           0    0.0

R

library(tidyverse)
PATH <- "http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv"
df <- read.csv(PATH)
tail(df)

#       recency history_segment history mens womens  zip_code newbie      channel       segment visit conversion spend
# 63995       7    1) $0 - $100   86.46    0      1     Urban      0          Web   Mens E-Mail     0          0     0
# 63996      10  2) $100 - $200  105.54    1      0     Urban      0          Web   Mens E-Mail     0          0     0
# 63997       5    1) $0 - $100   38.91    0      1     Urban      1        Phone   Mens E-Mail     0          0     0
# 63998       6    1) $0 - $100   29.99    1      0     Urban      1        Phone   Mens E-Mail     0          0     0
# 63999       1  5) $500 - $750  552.94    1      0 Surburban      1 Multichannel Womens E-Mail     0          0     0
# 64000       1  4) $350 - $500  472.82    0      1 Surburban      0          Web   Mens E-Mail     0          0     0

グループ分けした時の要約統計量

グループ分けを行い、欲しい列を抽出した時の、要約統計量を算出するコード。
PythonとRにおけるコードの対応関係は下記のようになります。
ここでは、dfchannelzip_codeでグルーピングを行い、recencyhistoryspendの平均値と最大値を算出しています。

Python

  • groupby(["hoge", "fuga"])
  • agg("mean","max")[["col1", "col2"]]
df_new = df.groupby(["channel","zip_code"]).agg(["mean","max"])[["recency","history","spend"]]

# df_new
#                         recency       history              spend
#                         mean     max  mean        max      mean      max
# channel      zip_code
# Multichannel Rural      4.773451  12  523.810743  2079.58  1.124389  328.36
#              Surburban  4.760173  12  521.458574  2809.79  1.357048  499.00
#              Urban      4.775813  12  519.422769  3215.97  1.549687  421.76
# Phone        Rural      5.827175  12  207.631497  2080.78  1.070033  499.00
#              Surburban  5.946969  12  200.612477  3345.93  0.809698  499.00
#              Urban      5.868908  12  203.447781  3003.48  0.958960  499.00
# Web          Rural      5.889335  12  202.806019  2039.13  1.336614  362.20
#              Surburban  5.944801  12  203.407815  2816.01  1.076664  499.00
#              Urban      5.864510  12  206.074893  3040.20  1.025505  499.00

R

  • group_by(hoge, fuga)
  • summarize_each(funs(mean, max), col1, col2)
df_new <- df %>%
  group_by(channel, zip_code) %>%
  summarise_each(funs(mean, max),
                 recency, history, spend)

# df_new
#   channel      zip_code  recency_mean history_mean spend_mean recency_max history_max spend_max
#   <fct>        <fct>            <dbl>        <dbl>      <dbl>       <int>       <dbl>     <dbl>
# 1 Multichannel Rural             4.77         524.      1.12           12       2080.      328.
# 2 Multichannel Surburban         4.76         521.      1.36           12       2810.      499
# 3 Multichannel Urban             4.78         519.      1.55           12       3216.      422.
# 4 Phone        Rural             5.83         208.      1.07           12       2081.      499
# 5 Phone        Surburban         5.95         201.      0.810          12       3346.      499
# 6 Phone        Urban             5.87         203.      0.959          12       3003.      499
# 7 Web          Rural             5.89         203.      1.34           12       2039.      362.
# 8 Web          Surburban         5.94         203.      1.08           12       2816.      499
# 9 Web          Urban             5.86         206.      1.03           12       3040.      499

データのソート

列を選択し、データのソートをするコード。
historyの値が昇順になるように並び替えています。

Python

  • sort_values(["hoge"])
df_new = df[df.history > 3000]
df_new = df_new.sort_values(["history"])

# df_new
#        recency history_segment  history  mens  womens   zip_code  newbie       channel        segment  visit  conversion  spend
# 43060        1     7) $1,000 +  3003.48     1       1      Urban       1         Phone      No E-Mail      0           0    0.0
# 52860        1     7) $1,000 +  3040.20     0       1      Urban       1           Web  Womens E-Mail      0           0    0.0
# 38680        1     7) $1,000 +  3215.97     1       1      Urban       1  Multichannel    Mens E-Mail      1           0    0.0
# 4579         2     7) $1,000 +  3345.93     1       1  Surburban       1         Phone      No E-Mail      0           0    0.0

R

  • arrange(hoge)
df_new <- df %>%
  filter(history > 3000) %>%
  arrange(history)

# df_new
#   recency history_segment history mens womens  zip_code newbie      channel       segment visit conversion spend
# 1       1     7) $1,000 + 3003.48    1      1     Urban      1        Phone     No E-Mail     0          0     0
# 2       1     7) $1,000 + 3040.20    0      1     Urban      1          Web Womens E-Mail     0          0     0
# 3       1     7) $1,000 + 3215.97    1      1     Urban      1 Multichannel   Mens E-Mail     1          0     0
# 4       2     7) $1,000 + 3345.93    1      1 Surburban      1        Phone     No E-Mail     0          0     0

インデックスを列に追加

データの1列目に、元データの行番号を追加するコード。
元の行番号を列に追加して、indexを振り直しています。

Python

  • insert(0, "id", df.index)

reset_index()でも可能ですが、場所を選びたい時はinsertの方が便利です。

df_new = df.copy()
df_new.insert(0, "id", df_new.index)
df_new = df_new[df_new.history > 3000]
df_new = df_new.reset_index(drop=True)

# df_new
#       id  recency history_segment  history  mens  womens   zip_code  newbie       channel        segment  visit  conversion  spend
# 0   4579        2     7) $1,000 +  3345.93     1       1  Surburban       1         Phone      No E-Mail      0           0    0.0
# 1  38680        1     7) $1,000 +  3215.97     1       1      Urban       1  Multichannel    Mens E-Mail      1           0    0.0
# 2  43060        1     7) $1,000 +  3003.48     1       1      Urban       1         Phone      No E-Mail      0           0    0.0
# 3  52860        1     7) $1,000 +  3040.20     0       1      Urban       1           Web  Womens E-Mail      0           0    0.0

R

  • rowid_to_column("id")
df_new <- df %>%
  rowid_to_column("id") %>%
  filter(history > 3000)

# df_new
#      id recency history_segment history mens womens  zip_code newbie      channel       segment visit conversion spend
# 1  4580       2     7) $1,000 + 3345.93    1      1 Surburban      1        Phone     No E-Mail     0          0     0
# 2 38681       1     7) $1,000 + 3215.97    1      1     Urban      1 Multichannel   Mens E-Mail     1          0     0
# 3 43061       1     7) $1,000 + 3003.48    1      1     Urban      1        Phone     No E-Mail     0          0     0
# 4 52861       1     7) $1,000 + 3040.20    0      1     Urban      1          Web Womens E-Mail     0          0     0

データの縦持ち

複数列のデータを1列にするコード。
id列・変数名列・変数列の3つの列を持つデータに変更しています。
Python、Rともに同じ結果になるようにしています。

Python

  • stack()
df_vertical = df[df.history > 3000]
df_vertical = pd.DataFrame(df_vertical.stack())
df_vertical = df_vertical.rename(columns={0: "values"})
df_vertical.index.names = ["id", "keys"]
df_vertical

# df_vertical.head(24)
#                              values
# id    keys
# 4579  recency                     2
#       history_segment   7) $1,000 +
#       history               3345.93
#       mens                        1
#       womens                      1
#       zip_code            Surburban
#       newbie                      1
#       channel                 Phone
#       segment             No E-Mail
#       visit                       0
#       conversion                  0
#       spend                       0
# 38680 recency                     1
#       history_segment   7) $1,000 +
#       history               3215.97
#       mens                        1
#       womens                      1
#       zip_code                Urban
#       newbie                      1
#       channel          Multichannel
#       segment           Mens E-Mail
#       visit                       1
#       conversion                  0
#       spend                       0

R

  • gather()
df_vertical <- df %>%
  rowid_to_column("id") %>%
  filter(history > 3000) %>%
  gather(key = keys, value = values, -id) %>%
  arrange(id)

# df_vertical %>% head(24)
#       id            keys       values
# 1   4580         recency            2
# 2   4580 history_segment  7) $1,000 +
# 3   4580         history      3345.93
# 4   4580            mens            1
# 5   4580          womens            1
# 6   4580        zip_code    Surburban
# 7   4580          newbie            1
# 8   4580         channel        Phone
# 9   4580         segment    No E-Mail
# 10  4580           visit            0
# 11  4580      conversion            0
# 12  4580           spend            0
# 13 38681         recency            1
# 14 38681 history_segment  7) $1,000 +
# 15 38681         history      3215.97
# 16 38681            mens            1
# 17 38681          womens            1
# 18 38681        zip_code        Urban
# 19 38681          newbie            1
# 20 38681         channel Multichannel
# 21 38681         segment  Mens E-Mail
# 22 38681           visit            1
# 23 38681      conversion            0
# 24 38681           spend            0

縦持ちの横持ち化

上記でstack及びgatherによって縦持ちにしたdf_verticalを横持ちに戻すコード。

Python

  • unstack()
df_horizontal = df_vertical.unstack()

# df_horizontal
#        values
# keys  recency history_segment  history mens womens   zip_code newbie       channel        segment visit conversion spend
# id
# 4579        2     7) $1,000 +  3345.93    1      1  Surburban      1         Phone      No E-Mail     0          0     0
# 38680       1     7) $1,000 +  3215.97    1      1      Urban      1  Multichannel    Mens E-Mail     1          0     0
# 43060       1     7) $1,000 +  3003.48    1      1      Urban      1         Phone      No E-Mail     0          0     0
# 52860       1     7) $1,000 +   3040.2    0      1      Urban      1           Web  Womens E-Mail     0          0     0

ただ、columnが2行になってしまうので、以下のようにすると元に戻ります。

cols = [v2 for v1, v2 in df_horizontal.columns.values]
df_horizontal.columns = cols

# df_horizontal
#       recency history_segment  history mens womens   zip_code newbie       channel        segment visit conversion spend
# id
# 4579        2     7) $1,000 +  3345.93    1      1  Surburban      1         Phone      No E-Mail     0          0     0
# 38680       1     7) $1,000 +  3215.97    1      1      Urban      1  Multichannel    Mens E-Mail     1          0     0
# 43060       1     7) $1,000 +  3003.48    1      1      Urban      1         Phone      No E-Mail     0          0     0
# 52860       1     7) $1,000 +   3040.2    0      1      Urban      1           Web  Womens E-Mail     0          0     0

R

  • spread()
df_horizontal <- df_vertical %>%
  spread(key = keys, value = values)

# df_horizontal
#      id      channel conversion history history_segment mens newbie recency       segment spend visit womens  zip_code
# 1  4580        Phone          0 3345.93     7) $1,000 +    1      1       2     No E-Mail     0     0      1 Surburban
# 2 38681 Multichannel          0 3215.97     7) $1,000 +    1      1       1   Mens E-Mail     0     1      1     Urban
# 3 43061        Phone          0 3003.48     7) $1,000 +    1      1       1     No E-Mail     0     0      1     Urban
# 4 52861          Web          0  3040.2     7) $1,000 +    0      1       1 Womens E-Mail     0     0      1     Urban

連載