Pythonを使ったRFM分析
今回はこれまでと趣向を変えて、サンプルデータを使った分析手法(RFM分析)について取り上げる。
RFM分析は、Recency(直近)、Frequency(頻度)、Monetary(購入額)の略であり、マーケティングの分野において、顧客をグループ化した上で優良顧客を抽出し、確度の高い施策を講じる際に用いられる。
今回サンプルデータとして、以下にアップされているものを利用した。
RFM-analysis/sample-orders.csv at master · joaolcorreia/RFM-analysis · GitHub
import pandas as pd import matplotlib.pyplot import datetime as dt # ファイルの読み込み df = pd.read_csv('sample-orders.csv',sep=',') df['order_date'] = pd.to_datetime(df['order_date']) df.head() # order_date order_id customer grand_total # 0 2011-09-07 CA-2011-100006 Dennis Kane 378 # 1 2011-07-08 CA-2011-100090 Ed Braxton 699 # 2 2011-03-14 CA-2011-100293 Neil Franz�sisch 91 # 3 2011-01-29 CA-2011-100328 Jasper Cacioppo 4 # 4 2011-04-08 CA-2011-100363 Jim Mitchum 21 NOW = dt.datetime(2014,12,31) # 顧客名を軸にデータをグループ化し、R、F、Mの3つの列を作成 rfmTable = df.groupby('customer').agg({'order_date': lambda x: (NOW - x.max()).days, # Recency 'order_id': lambda x: len(x), # Frequency 'grand_total': lambda x: x.sum()}) # Monetary Value rfmTable['order_date'] = rfmTable['order_date'].astype(int) rfmTable.rename(columns={'order_date': 'recency', 'order_id': 'frequency', 'grand_total': 'monetary_value'}, inplace=True) rfmTable.head() # frequency recency monetary_value # customer # Aaron Bergman 3 415 887 # Aaron Hawkins 7 12 1744 # Aaron Smayling 7 88 3050 # Adam Bellavance 8 54 7756 # Adam Hart 10 34 3249 # R、F、Mのそれぞれについて4段階でスコア付けをする関数を定義 def pct_rank_qcut(series, n): edges = pd.Series([float(i) / n for i in range(n + 1)]) f = lambda x: (edges >= x).argmax() return series.rank(pct=1).apply(f) # Recency rfmTable['rec_dec'] = pct_rank_qcut(rfmTable['recency'], 4) # Frequency(数が大きなものを1、小さなものを4とランク付け) freq_dec = pct_rank_qcut(rfmTable['frequency'], 4) rfmTable['freq_dec'] = 5 - freq_dec # Monetary(数が大きなものを1、小さなものを4とランク付け) mv_dec = pct_rank_qcut(rfmTable['monetary_value'], 4) rfmTable['mv_dec'] = 5 - mv_dec # R、F、Mの3つを組合せて、RFMスコアを作成 rfmTable['RFMClass'] = rfmTable.rec_dec.map(str) + rfmTable.freq_dec.map(str) + rfmTable.mv_dec.map(str) rfmTable.head() # frequency recency monetary_value rec_dec freq_dec \ # customer # Aaron Bergman 3 415 887 4 4 # Aaron Hawkins 7 12 1744 1 2 # Aaron Smayling 7 88 3050 3 2 # Adam Bellavance 8 54 7756 2 2 # Adam Hart 10 34 3249 2 1 # mv_dec RFMClass # customer # Aaron Bergman 4 444 # Aaron Hawkins 3 123 # Aaron Smayling 2 322 # Adam Bellavance 1 221 # Adam Hart 2 212 # Recencyの4つのグループにつき、購入金額の平均を比較 rec_dec_m = rfmTable.groupby(['rec_dec'])['monetary_value'].mean() rec_dec_m.plot(kind='bar') # Frequencyの4つのグループにつき、購入金額の平均を比較 freq_dec_m = rfmTable.groupby(['freq_dec'])['monetary_value'].mean() freq_dec_m.plot(kind='bar')
Recencyに対するチャート(縦軸:購入金額の平均)
Frequencyに対するチャート(縦軸:購入金額の平均)
Pandasを使った行列のセレクティングについて
pandasでのiloc[行, 列]を使った操作に関する備忘録。
特定のデータを行あるいは列から抜き出して表示したり、違う数値へ置き換えたりする方法について記す。
import numpy as np import pandas as pd # データフレームの作成 df = pd.DataFrame(data= [('2016-04-01', 10.2, 60.3), ('2016-04-02', 16.4, 71.7), ('2016-04-03', 11.1, 65.6), ('2016-04-04', 15.1, 73.2)]) df.columns = ['date', 'temp', 'humidity'] df # date temp humidity # 0 2016-04-01 10.2 60.3 # 1 2016-04-02 16.4 71.7 # 2 2016-04-03 11.1 65.6 # 3 2016-04-04 15.1 73.2 # データフレームから2行目のデータを抜き出し df.iloc[1, :] # date 2016-04-02 # temp 16.4 # humidity 71.7 # Name: 1, dtype: object # データフレームから上2行のデータのみを抜き出し df.iloc[:2] # date temp humidity # 0 2016-04-01 10.2 60.3 # 1 2016-04-02 16.4 71.7 # データフレームから2列目のデータ(気温)を抜き出し df.iloc[:, 1] # 0 10.2 # 1 16.4 # 2 11.1 # 3 15.1 # Name: temp, dtype: float64 # 指定した位置にあるデータを別のものに置き換える。 df.loc[df['date'] == '2016-04-01', 'temp'] = 12.3 df # date temp humidity # 0 2016-04-01 12.3 60.3 # 1 2016-04-02 16.4 71.7 # 2 2016-04-03 11.1 65.6 # 3 2016-04-04 15.1 73.2
行列の選択においてloc, iloc, ixという3つのメソッドが存在するが、以下のページによるとその違いは、
python - pandas iloc vs ix vs loc explanation, how are they different? - Stack Overflow
- loc: インデックスのラベル名に対応
- iloc: インデックスの順序に対応
- ix: 基本はlocのように振る舞うが、インデックスにラベル名が存在しない場合、ilocのように振る舞う
とのことらしい。
例えば、2列目のデータを抜き出す際に"iloc"を使用しているが、行(すなわちインデックスおよびラベル名)を指定してないため、"loc"は使えず、エラーが返される。また、同じ例で"ix"を使った場合、"loc"が使えないので"iloc"と同じ結果が得られることになる。
なお、今回のデータの選択処理については、以下サイトも参考にしている。
sinhrks.hatenablog.com
Pandasでの複数ファイル読み込み
指定したフォルダ内に格納されている複数のファイルを読み込み、データフレームを作成する方法についての備忘録。
# 必要なパッケージの読み込み import pandas as pd import numpy as np from datetime import date, datetime, timedelta import time import sys import glob import errno # ファイルに日付データが含まれている前提で、開始日と終了日を指定する。 # データフレーム作成時にインデックスとして使用。 start = datetime(2015, 6, 1) end = datetime(2016, 5, 31) rng = pd.date_range(start, end) # 次に関数を定義。xはパス名のうち省略したい部分を差す。 # データフレーム作成時、読み込みたいファイル名から、 # ディレクトリと拡張子を省いたものをカラム名とする。 def into_dataframe(path, x): files = glob.glob(path) df = pd.DataFrame() col = [] list_ = [] for name in files: try: # -4を入れることで拡張子を省く col.append(name[x:-4]) d = pd.read_csv(name, sep='\t') list_.append(d.ix[:, 1:]) except IOError as exc: if exc.errno != errno.EISDIR: raise df = pd.concat(list_, axis=1) df.index = rng df.columns = col return df # データを読み込むパス名およびxを指定 path = '/Users/xxx/Documents/files/*.tsv' df = into_dataframe(path, 100)
このように、ある程度ファイルのフォーマットが揃っている必要はあるが、複数のファイルを1つのデータフレームにまとめたいときに便利。
Pandasでのデータ集計
pandasを使ったデータフレームの成形について。meltやpivot_tableの使い方に関する備忘録。
例:
都市ID毎の日別気温、湿度データが与えられているが、各列にデータがまとめられている(例えば、気温の列に全ての都市IDに紐づくデータが一纏めになっている)とき、都市ID毎の気温、湿度データを取り出し、別の列にまとめ直す。
pandasを用いた操作は、次の3つのステップで実行する。
ステップ1:meltを使って、気温、湿度データを一列(value)にまとめる。あるいは、複数列で持っている値を行持ちに展開する。
ステップ2:IDとvariable名をくっつけ、keyという列をつくる。
ステップ3:pivot_tableを使って、key列を横方向へ展開する。あるいは、複数行で持っている値を列持ちに変換する。
import pandas as pd import datetime # データフレームの作成 df = pd.DataFrame(data=[('2016-04-01', 'a', 10.2, 60.3), ('2016-04-02', 'a', 16.4, 71.7), ('2016-04-01', 'b', 11.1, 65.6), ('2016-04-02', 'b', 15.1, 73.2)]) df.columns = ['date', 'id', 'temp', 'humidity'] # 成形前(df) # date id temp humidity # 0 2016-04-01 a 10.2 60.3 # 1 2016-04-02 a 16.4 71.7 # 2 2016-04-01 b 11.1 65.6 # 3 2016-04-02 b 15.1 73.2 # ステップ1 df1 = pd.melt(df, id_vars = ['date', 'id'], value_vars = ['temp', 'humidity']) # ステップ2 df1['key'] = df1['id'].map(str) + '_' + df1['variable'].map(str) df1 = df1.drop(['id', 'variable'], 1) df1['date'] = pd.to_datetime(df1['date']) # df1['date'] = [datetime.datetime.strptime(i, '%Y-%m-%d') for i in df1['date']] 少し回りくどい方法 df1.index = df1['date'] # ステップ3 df2 = df1.pivot_table(values='value', index='date', columns='key') # 成形後(df2) # key a_humidity a_temp b_humidity b_temp # date # 2016-04-01 60.3 10.2 65.6 11.1 # 2016-04-02 71.7 16.4 73.2 15.1
Pandasでの時系列操作
python、主にpandasの基本的な使い方について、備忘録として記述していく。
まずはタイトルの通り、時系列操作について。
例1
あるデータフレームに年(Year)列と月(Month)列データが入っているとき、
この2つを年月として合わせて、データフレームのインデックスに割り当てる。
import pandas as pd from datetime import date time df.index = df.apply(lambda x: datetime(int(x.Year), int(x.Month), 1), axis=1)
例2
2012年1月から2016年12月まで、60ヶ月のリストデータを作成する。
以下の例は月ごとに区切られたものだが、freqに'D'を指定すると日ごと、'Y'を指定すると年ごとになる。
import pandas as pd index = pd.date_range('2012-1', periods=60, freq='M')
例3
データフレームの'date'という列に日別の時系列データが入っているとき、
月曜日から順に0, 1, 2, と数字を割り当てたあと、5(土曜)、6(日曜)を休日として定義し、
'DayStatus'という列でラベリングする。
#曜日列を追加 df['DayOfWeek'] = df['date'].apply(lambda x: x.weekday()) #平日休日判定の関数定義 def day_status(x): if x <= 4: return 0 #平日 else: return 1 #休日 #平日休日判定本番 df['DayStatus'] = df['DayOfWeek'].apply(lambda x: day_status(x))
七月二十五日のこと(アイスランド)
ここからが北欧旅行記。朝9時にアイスランドのケプラビーク国際空港に到着。ニューヨークからは5時間半程度のフライト。機内で映画を楽しむ時間もないくらい。こんなにすぐアイスランドに行けるんやなと少し感動した。空港は噂に聞いたとおり小さめながらも近代的なつくり。シースルーの天井で光の入り具合もいい感じ。
14時半くらいに迎えのバスが来てレイキャビク市内へ移動。40分くらいの移動だったが、道中、目の前に広がっていたのはゴツゴツした溶岩と曇りがかった空で、じっと目を凝らしているとだんだん不安になってくる、そんな光景だった。
それでもレイキャビク市内は結構都会で、今回滞在した宿のあるLaugavegur通りにはブティックや飲食店が多数固まっていて、たくさんの人で賑わっていた。ストリートアートも盛んらしく、街の至るところでグラフィティを目にしたのも興味深かった。カラフルな建築物も多く見られ、結構アーティスティックな街だという印象を受けた。
街をぶらつきながら、前々から行ってみたかった12 TonarというCDショップへ。なんでもコーヒーを飲みながら店内に置いてあるCDを自由に視聴できることでも有名。早速手当たり次第にかき集めたCDを手に店長に声をかけてから試聴機へ。思えばここ数年、こうして店で新しい音楽をDigることをほとんどしていなかったことにハッと気づいた。最近の音楽の聴き方といえば、そのときの気分に合わせて何も考えずにSpotifyやYouTubeでオススメのプレイリストを垂れ流すというもので、新たに見つけたアーティストに感動して、じっくり一つの作品を聴きこむ経験が明らかに減ってきている。確かに音楽のジャンルも飽和してきてるし、なかなかビックリするような目新しい出会いには恵まれないかもしれないが、自分の知らなかったアイスランドの良質なアーティストを知ることができたし、この店で聴いたこと、見たこと、考えたことを通じて、忘れかけていたものを少しだけ取り戻せた気がする。
その他、有名なハットルグリムス教会やハルパ・レイキャヴィク・コンサートホールなどを巡り、夕食にはビストロで魚料理を堪能した。
七月二十四日のこと(ボストン〜ニューヨーク)
この日は昼過ぎのフライトでボストンからニューヨークへ向かう予定になっていて、午前中観光するのもしんどいし、前日の疲れも残っていたため、遅めのチェックアウトを経て少し早めに空港へ向かうことにした。しかし、移動中に早くもアクシデント発生。突然携帯に飛び込んできたメールを見ると飛行機の出発が30分ほど遅れるとのこと。ニューヨークでの約7時間のトランジット時間を使ってMOMAに行こうと計画していたが、雲行きが怪しくなった。