すのふら

日々の備忘録

【技術編】アイカツ!シリーズのCD売上から見えてくるもの

先週書いたアイカツ!のCD売上分析で使用したpytonコードをメモとして記載しておく。 分析自体については以下を参照ください。
snofra.hatenablog.com

今回のデータ分析について


f:id:snofra:20180611001537p:plain
今回はデータ量も大したことなかったので手動でデータをcsvに加工して作業をしていた。
JINの投稿データは収集に1時間かかってマジクソと思ったんだけど、クローラ作るほど何度もJINは利用しないので頑張った。

データの加工はpandasで簡単にやりつつ、描画はbokehにしてpandasのdataframeをそのまま取り込むことにした。

実装量はかなり少ない。変に自前でサマリする処理加えるよりも楽だったというのが正直なところ。


実装


ロード部

import pandas as pd
import numpy as np
import math
# bokeh系のライブラリ
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource,  Range1d, LinearAxis
output_notebook()

# csvファイルのロード
# アイカツ!のCD売上
df_aikatsu_cd = pd.read_csv('C:/xxxx/cd_sales.csv', parse_dates=[2], engine='python', encoding="utf-8")
# googleTrends
df_trend = pd.read_csv('C:/xxxx/multiTimeline.csv', parse_dates=[0], engine='python', encoding="utf-8")
# JINのアイカツでヒットした投稿数
df_jin = pd.read_csv('C:/xxxx/jin.csv', parse_dates=[0], engine='python', encoding="utf-8")

sales_day_all = df_aikatsu_cd.fillna('0')


加工部

# 発売月単位でサマリする
df = pd.DataFrame({'発売月' : sales_day_all["発売日"].dt.strftime('%Y-%m'),
                   '初動枚数' : sales_day_all.fillna('0')["初動枚数"].str.replace(',','').astype('int'),
                   '累計枚数' : sales_day_all.fillna('0')["累計枚数"].str.replace(',','').astype('int'),
                   '売上高' : (sales_day_all.fillna('0')["セールス(円)"].str.replace(',','').astype('int') /10000).round(0) # 売上高(万円)単位で出すため除算
                  })

df_groupby = df.groupby("発売月",as_index=False).sum()

# 発売年単位でサマリする
df_groupby_year = pd.DataFrame({'発売年' : sales_day_all["発売日"].dt.strftime('%Y'),
                                '初動枚数' : sales_day_all.fillna('0')["初動枚数"].str.replace(',','').astype('int'),
                                '累計枚数' : sales_day_all.fillna('0')["累計枚数"].str.replace(',','').astype('int'),
                                '売上高' : (sales_day_all.fillna('0')["セールス(円)"].str.replace(',','').astype('int')/100000000).round(4) # 売上高(億円)単位で出すため除算 
                               })

df_groupby_year = df_groupby_year.groupby("発売年",as_index=False).sum()

# googleTrendsを月単位でサマリ
df_trend_month = pd.DataFrame({'月' : df_trend["週"].dt.strftime('%Y-%m'),
                               'トレンド数' : df_trend.fillna('0')["トレンド数"].astype('int')
                             })

df_groupby_trend = df_trend_month.groupby("月",as_index=False).sum()

# googleTrendsを年単位でサマリ
df_trend_year = pd.DataFrame({'年' : df_trend["週"].dt.strftime('%Y'),
                             'トレンド数' : df_trend.fillna('0')["トレンド数"].astype('int')
                             })

df_groupby_trend_year = df_trend_year.groupby("年",as_index=False).sum()


# JINのアイカツでヒットした投稿数のサマリ
df_jin = pd.DataFrame({'投稿月' : df_jin["投稿日"].dt.strftime('%Y-%m'),
                       'count' : df_jin.fillna('0')["count"].astype('int')
                      })

df_groupby_jin = df_jin.groupby("投稿月",as_index=False).sum()

年単位、月単位のサマリはDataframeでやるのが一番簡単かなということで、Pandasのdataframeのgroupbyとsum()で一気に集計している。

bokehの表示上の都合で、売上高の桁調整をここでやる。 0が多すぎるとうまく表示されない。

pandasのdataframeの一部カラムだけ表示したいなーと思って調べたので、今回は特に使うことないがメモだけしておく。

# pandasのdataframeの一部のカラムだけ表示する
print(df.loc[:,['発売月','初動枚数','累計枚数']])

f:id:snofra:20180610015241p:plain
こんな感じで表示される。


曲線近似の算出

# 曲線近似の算出
# 累計枚数
regression = np.polyfit(df_groupby.index, df_groupby['累計枚数'], 1)

print(df_groupby.index*regression[0] + regression[1])

# 売上高
regression_sales_amount = np.polyfit(df_groupby.index, df_groupby['売上高'], 1)

print(df_groupby.index*regression_sales_amount[0] + regression_sales_amount[1])

曲線近似の算出はnumpyとscipyがあるようだけど、今回はnumpyで実装。
ailaby.com
qiita.com

正直、曲線にする必要は全然なかった。
グラフのプロットはprint文のとおり、CD売上月とCDの累計売上数or売上高で1次関数で算出している。


グラフのplot

時系列データを使ったCD売上推移

# CD売上推移のグラフplot
df_groupby["発売月"] = pd.to_datetime(df_groupby["発売月"])

# CD売上のplot
p = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title="CD売上推移")
p.line(df_groupby["発売月"], df_groupby['累計枚数'], line_width=3.5, color="red", alpha=0.5)
p.circle(df_groupby["発売月"], df_groupby['累計枚数'], fill_color="white", line_color="red", size=10)

# 曲線近似のplot
p.extra_y_ranges = {"graph2": Range1d(start=0, end=11200)}
p.line(df_groupby["発売月"], df_groupby.index*regression[0] + regression[1], line_width=1,color="blue", alpha=0.5, y_range_name="graph2")

show(p)

グラフのX軸と、y軸の説明、グラフの凡例は多少加工しているが以下のような感じで出力される。
f:id:snofra:20180529000900p:plain

かなりシンプルに実装できる。 Dataframeをそのままグラフにplotできるbokehだからこそのシンプルさだと思う。

苦戦したのはX軸をうまく読んでくれないというのがあった。
というのも時系列データにする場合、型をちゃんと日付型にする必要があったんだけど、daraframeでは文字列型だったから。
日付型にto_datetimeしてから再度入れなおすという対応をした。

盛り上がっていた時期を調べるグラフ

df_groupby_trend["月"] = pd.to_datetime(df_groupby_trend["月"])
p = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title="盛り上がっていた時期は?")

# CD売上のplot
p.line(df_groupby["発売月"], df_groupby['累計枚数'], line_width=3.5, color="red", alpha=0.5)
p.circle(df_groupby["発売月"], df_groupby['累計枚数'], fill_color="white", line_color="red", size=10)

# googletrendsのplot
p.extra_y_ranges = {"graph2": Range1d(start=1, end=500)}
p.line(df_groupby_trend["月"], df_groupby_trend['トレンド数'], line_width=3.5, color="green", alpha=0.5, y_range_name="graph2")

show(p)

これもグラフのX軸と、y軸の説明、グラフの凡例は多少加工しているが以下のような感じで出力される。
f:id:snofra:20180530002546p:plain

JINとの比較グラフ

df_groupby_jin["投稿月"] = pd.to_datetime(df_groupby_jin["投稿月"])
p = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title="GoogleTrendsの検索数vsJINの記事数")

# googletrendsのplot
df_groupby_trend["月"] = pd.to_datetime(df_groupby_trend["月"])
p.line(df_groupby_trend["月"], df_groupby_trend['トレンド数'], line_width=3.5, color="green", alpha=0.5)

# JINの投稿数のplot
p.extra_y_ranges = {"graph2": Range1d(start=0, end=20)}
p.line(df_groupby_jin["投稿月"], df_groupby_jin['count'], line_width=3.5, color="blue", alpha=0.5, y_range_name="graph2")
p.add_layout(LinearAxis(y_range_name="graph2"), 'right')

show(p)

基本は前のものと同じなんだけど、このグラフはグラフの右側にメモリを表示するようにした。メモリがあったほうが分かりやすいかなという判断。
p.add_layout(LinearAxis(y_range_name="graph2"), 'right')


折れ線グラフを棒グラフにしたパターンも試してみているので、メモしておく

df_groupby_trend["月"] = pd.to_datetime(df_groupby_trend["月"])
df_groupby_jin["投稿月"] = pd.to_datetime(df_groupby_jin["投稿月"])
p = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title="GoogleTrendsの検索数vsJINの記事数")

# googletrendsのplot
p.line(df_groupby_trend["月"], df_groupby_trend['トレンド数'], line_width=3.5, color="green", alpha=0.5)

# JINの投稿数のplot
p.extra_y_ranges = {"graph2": Range1d(start=0, end=20)}
p.vbar(x=df_groupby_jin["投稿月"], width=1, bottom=0,
       top=df_groupby_jin['count'], color="blue", y_range_name="graph2")

show(p)

その場合はこのようにplotされる。
f:id:snofra:20180610024701p:plain

相関係数の算出

# 相関係数の算出
correlation = np.corrcoef(df_groupby_trend_year["トレンド数"].values.tolist(), df_groupby_year["累計枚数"].values.tolist())
print(correlation[0,1])

相関係数を出すために各dataframeをarrayにしている。 月単位も年単位も同じやろってことで年単位にしてる。

numpy.corrcoef — NumPy v1.14 Manual

f:id:snofra:20180610233329p:plain
相関係数0.75はまあまあ相関しているのかなーっと。

売上高の算出

p = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title="CD売上高推移")

# 売上高のplot
p.line(df_groupby["発売月"], df_groupby['売上高'], line_width=3.5, color="red", alpha=0.5)
p.circle(df_groupby["発売月"], df_groupby['売上高'], fill_color="white", line_color="red", size=10)

# 曲線近似のplot
p.extra_y_ranges = {"graph2": Range1d(start=1000, end=2000)}
p.line(df_groupby["発売月"], df_groupby.index*regression_sales_amount[0] + regression_sales_amount[1], line_width=1,color="graph2", alpha=0.5, y_range_name="foo")

show(p)

グラフの実装は前と同じなので、言うことは特にない。
f:id:snofra:20180605001140p:plain


売上高の算出

# 年別売上高
#売上高でソートしてdataframeのindex振り直し
df_groupby_year_tmp = df_groupby_year.sort_values('売上高').reset_index(drop=True)

# 売上高のplot
p = figure(plot_width=400, plot_height=500, title="年別売上高")

p.hbar(y=df_groupby_year_tmp.index, height=0.5, left=0,
       right=df_groupby_year_tmp['売上高'], color="firebrick")

show(p)

plotするときにソートができそうになかったので、事前にソートを実施。
その時にdataframeのindexを振りなおさないと、結局plotしたときにソートが意味なくなってしまうので、dataframeのindexを振りなおして、y軸にindexを設定している。

ただこれをやるとy軸のメモリの凡例が0~6になるという問題があって、俺は結局グラフの加工でカバーすることにした。
うまい方法ないのかなー。
f:id:snofra:20180605004057p:plain


実装

簡単なplotなのでそんなに実装に時間はかかっていないけど、加工するのめんどい。
けど、加工したほうが見やすいっていうのをここのブログ見て理解した。

speakerdeck.com

shinyorke.hatenablog.com

今まではplotしてはいできたって、実装視点でしか見てなかったんだけど、実際はあれじゃわかりにくかったよなーと。
グラフ中に分かりやすく書いてあげるってのが必要だと思ってた。
気づかせてくれてありがとうございますっていう気持ち。