Edited at

Why are you using Python? Rによる自動集計ガイド

※タイトルで煽るのは良くないと思ったのでタイトルを変えました。

まだExcelで消耗してるの?Pythonによる自動集計ガイド 基礎編 - Qiitaを読んでいて(Rのが絶対便利…!)というお気持ちが強まってきたので勢い余って書きました。


はじめに

PythonはColaboratoryで手軽に試せて非常に良いです。実は、RもColaboratoryから使うことができます。ColaboratoryにはRのカーネルが既に入っているのですが、表から見えないようになっているだけなのです。

そこで、見えるようにしたものを用意しました。

このノートブックを使えば、Rだってすぐ試せます(もうちょっと詳しい説明はColaboratoryでRやSwiftを使う - Qiitaをどうぞ)。

試して下さい。今すぐ。


使用するパッケージ

主にdplyrを使います。

dplyrはpandasのように、いや、(たぶん)pandasよりずっと柔軟に、そして簡単にデータ処理が行えるRのパッケージです

dplyrはColaboratoryなら既に入っています。Colaboratory以外からRを使っている場合は、次の関数を実行してパッケージをインストールして下さい。

install.packages("dplyr")

その後、パッケージを使うためにロードしておきましょう。

library(dplyr)


データの読み込み

Web上にある.xlsxファイルは、rioパッケージのimport()関数を使えば直接読み込むことができます(ローカルにあるファイルならreadxlパッケージを使うと良いでしょう)。

rioはColaboratoryには入っていないのでインストールが必要です。

install.packages("rio")

この関数は1回しか使わないので、今回はパッケージをロードせずに関数を使用してみます。

df <- rio::import("https://pbpython.com/extras/excel-comp-data.xlsx")

head()でデータの先頭を確認してみます。

head(df)

## (一部略...)

## postal-code Jan Feb Mar
## 1 28752 10000 62000 35000
## 2 38365 95000 45000 35000
## 3 76517 91000 120000 35000
## 4 46021 45000 120000 10000
## 5 49681 162000 120000 35000
## 6 62785 150000 120000 35000

手元にデータを落とさずにデータの読み込みができました。

さて、pandasにこんなことはできるでしょうか?

import pandas as pd

df = pd.read_excel("https://pbpython.com/extras/excel-comp-data.xlsx")
print(df.head())

##    account                         name  ...       Feb    Mar

## 0 211829 Kerluke, Koepp and Hilpert ... 62000 35000
## 1 320563 Walter-Trantow ... 45000 35000
## 2 648336 Bashirian, Kunde and Price ... 120000 35000
## 3 109996 D'Amore, Gleichner and Bode ... 120000 10000
## 4 121213 Bauch-Goldner ... 120000 35000
##
## [5 rows x 9 columns]

できましたね!!!余分なパッケージも不要だしpandas便利ですね!!!!!


集計列の追加

気を取り直してdplyrパッケージを使い、JanからMarまでの合計列を追加してみます。

新しい列はmutate()で追加します。

head(mutate(df, total = Jan + Feb + Mar))

## (一部略...)  

## postal-code Jan Feb Mar total
## 1 28752 10000 62000 35000 107000
## 2 38365 95000 45000 35000 175000
## 3 76517 91000 120000 35000 246000
## 4 46021 45000 120000 10000 175000
## 5 49681 162000 120000 35000 317000
## 6 62785 150000 120000 35000 305000

よりdplyrらしい書き方をするなら、パイプ演算子%>%を使いましょう。この演算子は、パイプの前の処理結果をパイプの後の関数の第一引数として渡すという仕事をします。つまり、上記の例は次のように記述できます。

df %>% 

mutate(total = Jan + Feb + Mar) %>%
head()

このように行単位で途中の処理を記述することができるので、処理を追加したり、削除したりといった試行錯誤が非常にやりやすいという利点があります。

結果を再度使うため、変数に代入しておきます。

df2 <- df %>% 

mutate(total = Jan + Feb + Mar)

ところで、Rの代入演算子は右辺への代入もできるので、次のような書き方もできます。右辺への代入は好みが分かれるところですが、パイプ演算子を使った試行錯誤の後に変数へ代入するような場合には便利です。処理の頭にカーソルを戻さなくていいので、書いていて気持ちいいという効果もあります。

df %>% 

mutate(total = Jan + Feb + Mar) %>%
head() -> df2

また、もとの変数を更新するのであれば、magrittrパッケージの%<>%演算子を使ってより洗練された書き方をすることもできます。

library(magrittr)

df %<>%
mutate(total = Jan + Feb + Mar) %>%
head()


集計行を加える

列の集約値は、summarise()関数で得られます。

df2 %>% 

summarise(
sum = sum(Jan),
mean = mean(Jan),
min = min(Jan),
max = max(Jan),
)

##       sum     mean   min    max

## 1 1462000 97466.67 10000 162000

summarise_atを使えばより洗練された書き方をすることもできます。この例ではJanを繰返し書かなくて良くなるので、見た目がスッキリします。

df2 %>% 

summarise_at(
vars(Jan),
list(~sum, ~mean, ~min, ~max)
)

##       sum     mean   min    max

## 1 1462000 97466.67 10000 162000

複数列に複数の集計を適用するなんてことも簡単です。vars()は複数列を簡単に選択するための色々な方法を備えていますが、例えばJan:MarのようにするとJanからMarまでの3列をまとめて選択できます。

df2 %>% 

summarise_at(
vars(Jan:Mar),
list(~sum, ~mean)
)

##   Jan_sum Feb_sum Mar_sum Jan_mean Feb_mean Mar_mean

## 1 1462000 1507000 717000 97466.67 100466.7 47800

ちなみにJan:Marみたいな選択はpandasでもできます。pandas便利ですね!(複数の関数は.aggで適用できますが、組み込み関数にないmeanは文字列として指定しなければならない点に注意が必要です。)

print(df.loc[:, "Jan":"Mar"].agg([sum, 'mean']))

##                Jan           Feb       Mar

## sum 1.462000e+06 1.507000e+06 717000.0
## mean 9.746667e+04 1.004667e+05 47800.0

再度気を取り直してRに戻ります。もし名前付きベクトルとして結果がほしければ、unlist()します。

df2 %>% 

summarise_at(
vars(Jan:Mar),
~sum(.)
) %>% unlist()

##     Jan     Feb     Mar 

## 1462000 1507000 717000

Janからtotalまでの合計を計算して、もとのデータフレームにつなげてみましょう。それにはbind_rows()を使います。

df2 %>% 

summarise_at(
vars(Jan:total),
~sum(.)
) -> df_total
df2 %>%
bind_rows(df_total) %>%
tail

## (一部略...)

## city state postal-code Jan Feb Mar total
## 11 Rosaberg Tenessee 47743 45000 120000 55000 220000
## 12 Norbertomouth NorthDakota 31415 150000 10000 162000 322000
## 13 East Davian Iowa 72686 162000 120000 35000 317000
## 14 Goodwinmouth RhodeIsland 31919 55000 120000 35000 210000
## 15 Kathryneborough Delaware 27933 150000 120000 70000 340000
## 16 <NA> <NA> NA 1462000 1507000 717000 3686000

いかがでしたか?

ここで終わろうかと思いましたが、もうちょっとだけ続きます。


データをイケてる感じにする

ところで今回例として扱ったデータの形式はイマイチです。

よく見ると「月」が列名になってしまっています。月が増えたら横に伸びるのでしょうか?このような何らかの属性が横に展開されてしまっているデータ形式は横持ちと呼ばれます。

データを横持ちにしておくと、表がコンパクトにまとまるのでなんとなく「整理してやったぞ」感が出ます。また、紙にデータを記入する場合は大抵横持ち形式で記入すると思いますので、そのままExcelに入力したら横持ちデータの完成です。まあ何やかんやで世の中には横持ちのデータが沢山あるわけです。

しかし、横持ちのデータは一般的に機械的な処理に向きません。で、どうするかというとデータを縦持ちにします。縦持ちのデータは整然データ、あるいはtidy dataなどとも呼ばれます。

縦持ちのデータはどういうものかというと、ざっくりいうと「同じ属性のデータは同じ列に入れる」というルールに則って整理されたデータです。

例えば先程の例であれば、列名であったJan, Feb, Marを変数としてmonthという列に入れて、数値はvalueという列に入れると縦持ちになります。その名の通り縦に長くなるわけです。

Rでは、tidyの名を関するtidyrパッケージを使うとこの種のデータ変換を容易に行えます。横持ちデータはgather()で縦持ちに変換できます。

library(tidyr)

df3 <- df %>%
gather(key = month, value = value, Jan:Mar)
df3 %>% head(10)

## (一部略...)

## postal-code month value
## 1 28752 Jan 10000
## 2 38365 Jan 95000
## 3 76517 Jan 91000
## 4 46021 Jan 45000
## 5 49681 Jan 162000
## 6 62785 Jan 150000
## 7 18008 Jan 62000
## 8 53461 Jan 145000
## 9 64415 Jan 70000
## 10 46308 Jan 70000

一旦データを縦持ちにしてしまえば、dplyrを使ったデータ変換や集計といった操作は、非常にシンプルかつ直感的、可読性に優れた状態で記述可能となります。

df3 %>% 

group_by(month) %>%
summarise(sum_value = sum(value))

## # A tibble: 3 x 2

## month sum_value
## <chr> <dbl>
## 1 Feb 1507000
## 2 Jan 1462000
## 3 Mar 717000

で、pandasにこんなことができますか?ということが気になるわけです。

print(df.melt(value_vars=['Feb', 'Mar', 'Jan']).head(10))

##   variable   value

## 0 Feb 62000
## 1 Feb 45000
## 2 Feb 120000
## 3 Feb 120000
## 4 Feb 120000
## 5 Feb 120000
## 6 Feb 120000
## 7 Feb 95000
## 8 Feb 95000
## 9 Feb 120000

できますね!!!!!!!!!!

ただ、value_varsを指定するときに'Feb':'Jan'みたいな書き方はできないようでした。この点はRに軍配が上がりそうです。


まとめ

今回紹介したような基本的な集計なら、Rを使ってもPythonを使っても大きな差はないので、どちらでも好きな方でやれば良いと思います。

しかしRは寛容ですから、RからPythonを使うこともできます(cf. reticulateパッケージでRからPythonを使う - Qiita)。どちらを使うべきかはもうお分かりですね?