pacman::p_load(tidyverse, readxl, ggthemes, gt, gtExtras, patchwork, knitr)6 第6回 売れる商品を入荷しよう
週次集計POSデータを使って、他の店で売れている商品を見つけ出す。
6.1 準備
パッケージとデータを準備します。
第6回ファイルで使うデータはchp6.xlsxです。 まずパッケージを読み込みます。
次にここで用いるchp6.xlsxのシート名を確認します。
readxl::excel_sheets("data/chp6.xlsx")[1] "小魚くんシリーズ週次データ" "ピボットテーブル"
[3] "図6-2・図6-6・図6-9・図6-13" "表6-2・図6-5"
[5] "表6-3・図6-8" "表6-4・図6-11"
[7] "表6-6・図6-15" "表6-7・図6-17"
たくさんシートがあるMS Excelファイルですが、1番目の「小魚くんシリーズ週次データ」を読み込みます。
df <- readxl::read_excel("data/chp6.xlsx", sheet = 1)
glimpse(df)Rows: 79
Columns: 9
$ 対象週 <chr> "2023/10/30週", "2023/11/06週", "2023/11/13週", "2023/1…
$ 対象店舗数 <dbl> 102, 102, 102, 102, 102, 102, 102, 102, 102, 103, 103, …
$ 来店客数 <dbl> 2549796, 2549898, 2549796, 2549765, 2549898, 2550102, 2…
$ 商品名 <chr> "定番味", "定番味", "定番味", "定番味", "定番味", "定番…
$ 出現店舗数 <dbl> 80, 81, 81, 81, 81, 80, 80, 80, 80, 80, 80, 81, 81, 81,…
$ 出現店来店客数 <dbl> 2000080, 2025081, 2024919, 2025081, 2025000, 1999920, 1…
$ 売上金額 <dbl> 2065350, 1988920, 2027100, 1936000, 2040620, 1947770, 2…
$ 売上個数 <dbl> 13770, 13475, 13515, 12840, 13514, 12730, 13474, 13239,…
$ 平均価格 <dbl> 150.0, 147.6, 150.0, 150.8, 151.0, 153.0, 149.1, 151.7,…
このデータフレームには、
- 対象週 : 文字列
- 対象店舗数 : 数値
- 来店客数 : 数値
- 商品名 : 文字列
- 出現店舗数 : 数値
- 出現店来店客数 : 数値
- 売上金額 : 数値
- 売上個数 : 数値
- 平均価格 : 数値
の9つの変数が含まれています。
それぞれの詳細はテキストp.183を参照してください。
商品名をテーブルにしてみます。 「しびれ味」だけ測定期間の途中で販売が開始されたため,他の商品とはデータの個数が異なります。
6.2 出店カバー率
ある商品売れた店舗の数を全店舗数で除したものを出店カバー率と定義します。
\[ \text{出店カバー率} = \frac{\text{出現店舗数}}{\text{対象店舗数}} \times 100 \]
出店カバー率を計算して、dfに追加します。
df <- df |>
mutate(
出店カバー率 = 出現店舗数 / 対象店舗数 * 100
)これを週ごとと商品ごとに集計してみます。
df_cover <- df |>
group_by(対象週, 商品名) |>
summarise(
平均出店カバー率 = mean(出店カバー率)
) |>
ungroup()週と商品名ごとに出店カバー率の平均値を計算したので、商品名を列にして、列の順番を変えて、表として出力します。 セルの値ごとに色を濃くすることで、店頭カバー率の推移が分かりやすくしています。
df_cover |>
pivot_wider(names_from = 商品名, values_from = 平均出店カバー率) |> # ワイド型に
select(対象週, クリスマス限定, こだわり味, しびれ味, ピリ辛味, 激辛味, 定番味) |> # 列の順番を変更
gt() |> # 表を作成
fmt_number(columns = 2:7, decimals = 2) |> # 2〜7列を小数点2桁に
fmt_missing(columns = 2:7, missing_text = "") |>
gt_color_rows(2:7, domain = c(19, 100), palette = "ggsci::blue_material") |>
tab_header(title = "表6-2 6種の菓子の店頭カバー率の推移") |> # タイトルをつける
gt_theme_pff() # テーマを適用| 表6-2 6種の菓子の店頭カバー率の推移 | ||||||
|---|---|---|---|---|---|---|
| 対象週 | クリスマス限定 | こだわり味 | しびれ味 | ピリ辛味 | 激辛味 | 定番味 |
| 2023/10/30週 | 65.69 | 75.49 | 49.02 | 19.61 | 78.43 | |
| 2023/11/06週 | 65.69 | 75.49 | 49.02 | 20.59 | 79.41 | |
| 2023/11/13週 | 68.63 | 75.49 | 50.00 | 19.61 | 79.41 | |
| 2023/11/20週 | 60.78 | 73.53 | 50.00 | 21.57 | 79.41 | |
| 2023/11/27週 | 60.78 | 75.49 | 49.02 | 33.33 | 79.41 | |
| 2023/12/04週 | 77.45 | 75.49 | 19.61 | 50.00 | 45.10 | 78.43 |
| 2023/12/11週 | 77.45 | 73.53 | 21.57 | 50.00 | 50.98 | 78.43 |
| 2023/12/18週 | 76.47 | 75.49 | 22.55 | 50.00 | 58.82 | 78.43 |
| 2023/12/25週 | 59.80 | 76.47 | 29.41 | 49.02 | 62.75 | 78.43 |
| 2024/01/01週 | 44.66 | 73.79 | 32.04 | 49.51 | 63.11 | 77.67 |
| 2024/01/08週 | 44.66 | 75.73 | 33.01 | 49.51 | 65.05 | 77.67 |
| 2024/01/15週 | 45.63 | 75.73 | 36.89 | 49.51 | 69.90 | 78.64 |
| 2024/01/22週 | 46.60 | 73.79 | 40.78 | 48.54 | 69.90 | 78.64 |
| 2024/01/29週 | 48.54 | 75.73 | 43.69 | 48.54 | 69.90 | 78.64 |
「クリスマス限定」はクリスマスを境に店頭カバー率が激減し、「しびれ味」と「激辛味」は増加傾向で、他の商品は安定しているようです。
つぎに、横軸を週、縦軸を出店カバー率の平均値としたグラフにしてみます。 ggplot()関数で、group = 商品名とすることで、商品ごとの折れ線グラフを作ります。
df_cover |>
ggplot() + aes(x = 対象週, y = 平均出店カバー率, color = 商品名, group = 商品名) +
geom_line() + geom_point() + # 線と点を描く
theme_calc(base_family = "HiraKakuPro-W3") + # テーマを適用
# X軸のラベルを45度回転
labs(title = "図6-5 6種の菓子の店頭カバー率の推移のグラフ") +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_fill_tableau(name = "Tableau 20")
6.3 PI値
PI値(Purchase Index)は次のように定義されます。
\[ \text{PI値} = \frac{\text{売上個数}}{\text{来店客数}} \times 1000(人) \]
PI値を計算して、dfに追加します。
df <- df |>
mutate(
PI値 = 売上個数 / 来店客数 * 1000
)これを週ごとと商品ごとに集計してみます。 ほぼ先ほどの集計表のコードのコピペですが、PI値に変更しています。
df_pi <- df |>
group_by(対象週, 商品名) |>
summarise(
平均PI値 = mean(PI値)
) |>
ungroup()`summarise()` has grouped output by '対象週'. You can override using the
`.groups` argument.
# 作表
df_pi |>
pivot_wider(names_from = 商品名, values_from = 平均PI値) |>
ungroup() |>
select(対象週, クリスマス限定, こだわり味, しびれ味, ピリ辛味, 激辛味, 定番味) |>
gt() |>
fmt_number(columns = 2:7, decimals = 2) |>
fmt_missing(columns = 2:7, missing_text = "") |>
gt_color_rows(2:7, domain = c(0, 7), palette = "ggsci::blue_material") |>
tab_header(title = "表6-3: 6種の菓子の数量PIの推移") |>
tab_options(
heading.title.font.size = "normal",
table.font.size = "small"
) |>
gt_theme_pff()| 表6-3: 6種の菓子の数量PIの推移 | ||||||
|---|---|---|---|---|---|---|
| 対象週 | クリスマス限定 | こだわり味 | しびれ味 | ピリ辛味 | 激辛味 | 定番味 |
| 2023/10/30週 | 4.70 | 4.00 | 4.50 | 3.50 | 5.40 | |
| 2023/11/06週 | 4.80 | 4.10 | 4.50 | 3.60 | 5.28 | |
| 2023/11/13週 | 4.90 | 4.10 | 4.50 | 4.00 | 5.30 | |
| 2023/11/20週 | 4.30 | 3.90 | 4.40 | 4.10 | 5.04 | |
| 2023/11/27週 | 5.00 | 4.00 | 4.60 | 4.50 | 5.30 | |
| 2023/12/04週 | 5.70 | 3.90 | 0.80 | 4.50 | 4.80 | 4.99 |
| 2023/12/11週 | 5.90 | 3.90 | 0.70 | 4.60 | 5.10 | 5.28 |
| 2023/12/18週 | 5.00 | 4.00 | 1.20 | 4.50 | 5.00 | 5.19 |
| 2023/12/25週 | 3.90 | 4.10 | 1.80 | 4.60 | 5.00 | 5.40 |
| 2024/01/01週 | 3.88 | 4.00 | 2.00 | 4.60 | 4.80 | 5.20 |
| 2024/01/08週 | 3.60 | 3.90 | 2.50 | 4.40 | 4.20 | 5.30 |
| 2024/01/15週 | 4.50 | 4.00 | 3.20 | 4.60 | 4.20 | 5.30 |
| 2024/01/22週 | 4.54 | 3.90 | 4.10 | 4.60 | 3.80 | 5.08 |
| 2024/01/29週 | 4.20 | 3.90 | 4.30 | 4.50 | 3.50 | 5.20 |
PI値のグラフを描いてみます。
df_pi |>
ggplot() + aes(x = 対象週, y = 平均PI値, color = 商品名, group = 商品名) +
geom_line() + geom_point() +
theme_bw(base_family = "HiraKakuPro-W3") +
labs(title = "図6-8 6種の菓子の数量PIの推移のグラフ") +
# X軸のラベルを45度回転
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_fill_tableau(name = "Tableau 20")
6.3.1 金額PI値
金額PI値は次のように定義されます。
\[ \text{金額PI値} = \frac{\text{売上金額}}{\text{来店客数}} \times 1000(人) \]
金額PI値を計算して、dfに追加します。
df <- df |>
mutate(
金額PI値 = 売上金額 / 来店客数 * 1000
)これを週ごとと商品ごとに集計してみます。
df_kinpi <- df |>
group_by(対象週, 商品名) |>
summarise(
平均金額PI値 = mean(金額PI値)
) |>
ungroup()`summarise()` has grouped output by '対象週'. You can override using the
`.groups` argument.
# 作表
df_kinpi |>
pivot_wider(names_from = 商品名, values_from = 平均金額PI値) |>
ungroup() |>
select(対象週, クリスマス限定, こだわり味, しびれ味, ピリ辛味, 激辛味, 定番味) |>
gt() |>
fmt_number(columns = 2:7, decimals = 2) |>
fmt_missing(columns = 2:7, missing_text = "") |>
gt_color_rows(2:7, domain = c(100, 1000), palette = "ggsci::blue_material") |>
tab_header(title = "表6-4: 6種の菓子の金額PIの推移") |>
tab_options(
heading.title.font.size = "normal",
table.font.size = "small"
) |>
gt_theme_pff()| 表6-4: 6種の菓子の金額PIの推移 | ||||||
|---|---|---|---|---|---|---|
| 対象週 | クリスマス限定 | こだわり味 | しびれ味 | ピリ辛味 | 激辛味 | 定番味 |
| 2023/10/30週 | 756.70 | 875.98 | 679.49 | 563.48 | 810.01 | |
| 2023/11/06週 | 758.43 | 902.04 | 679.53 | 568.82 | 780.00 | |
| 2023/11/13週 | 779.10 | 893.79 | 670.50 | 647.99 | 795.00 | |
| 2023/11/20週 | 692.37 | 850.28 | 655.66 | 647.86 | 759.29 | |
| 2023/11/27週 | 799.97 | 876.04 | 694.63 | 715.53 | 800.28 | |
| 2023/12/04週 | 912.03 | 865.77 | 127.19 | 670.48 | 777.57 | 763.80 |
| 2023/12/11週 | 949.91 | 861.88 | 112.01 | 690.00 | 826.20 | 788.15 |
| 2023/12/18週 | 809.37 | 883.29 | 191.86 | 678.96 | 804.37 | 786.93 |
| 2023/12/25週 | 209.29 | 893.76 | 287.99 | 685.37 | 795.03 | 804.63 |
| 2024/01/01週 | 243.13 | 872.00 | 324.00 | 694.60 | 768.00 | 774.80 |
| 2024/01/08週 | 169.23 | 850.30 | 405.04 | 655.65 | 667.85 | 789.75 |
| 2024/01/15週 | 246.99 | 871.98 | 505.58 | 694.60 | 676.19 | 789.69 |
| 2024/01/22週 | 199.51 | 854.08 | 660.08 | 685.40 | 615.61 | 788.83 |
| 2024/01/29週 | 182.01 | 854.13 | 683.72 | 675.02 | 553.02 | 780.00 |
金額PI値のグラフを描いてみます。
df_kinpi |>
ggplot() + aes(x = 対象週, y = 平均金額PI値, color = 商品名, group = 商品名) +
geom_line() + geom_point() +
labs(title = "図6-11 6種の菓子の金額PIの推移のグラフ") +
theme_bw(base_family = "HiraKakuPro-W3") +
# X軸のラベルを45度回転
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_fill_tableau(name = "Tableau 20")
6.4 隠れヒット商品を見つける
特定店舗でのみ馬鹿売れしている隠れヒット商品を探すために出現店舗数ベースのPI値を調べる。 まず、出現店舗数ベースの数量PIと金額PIを計算します。
df <- df |>
mutate(
出現店数量PI = 売上個数 / 出現店来店客数 * 1000,
出現店金額PI = 売上金額 / 出現店来店客数 * 1000
)それぞれの出現店ベースの尺度を表にします。
df_shutu_pi <- df |>
group_by(対象週, 商品名) |>
summarise(
平均出現店数量PI = mean(出現店数量PI)
) |>
ungroup()`summarise()` has grouped output by '対象週'. You can override using the
`.groups` argument.
df_shutu_pi |>
pivot_wider(names_from = 商品名, values_from = 平均出現店数量PI) |>
ungroup() |>
select(対象週, クリスマス限定, こだわり味, しびれ味, ピリ辛味, 激辛味, 定番味) |>
gt() |>
fmt_number(columns = 2:7, decimals = 2) |>
fmt_missing(columns = 2:7, missing_text = "") |>
gt_color_rows(2:7, domain = c(3, 21), palette = "ggsci::blue_material") |>
tab_header(title = "表6-6: 6種類のスナック菓子の出現店・数量PIの推移") |>
tab_options(
heading.title.font.size = "normal",
table.font.size = "small"
) |>
gt_theme_pff()| 表6-6: 6種類のスナック菓子の出現店・数量PIの推移 | ||||||
|---|---|---|---|---|---|---|
| 対象週 | クリスマス限定 | こだわり味 | しびれ味 | ピリ辛味 | 激辛味 | 定番味 |
| 2023/10/30週 | 7.16 | 4.08 | 9.18 | 17.85 | 6.88 | |
| 2023/11/06週 | 7.31 | 4.94 | 9.18 | 17.49 | 6.65 | |
| 2023/11/13週 | 7.14 | 4.53 | 9.00 | 20.40 | 6.67 | |
| 2023/11/20週 | 7.07 | 4.82 | 8.80 | 19.01 | 6.34 | |
| 2023/11/27週 | 8.23 | 4.08 | 9.38 | 13.50 | 6.67 | |
| 2023/12/04週 | 7.36 | 4.70 | 4.08 | 9.00 | 10.64 | 6.37 |
| 2023/12/11週 | 7.62 | 4.42 | 3.25 | 9.20 | 10.00 | 6.74 |
| 2023/12/18週 | 6.54 | 4.42 | 5.32 | 9.00 | 8.50 | 6.62 |
| 2023/12/25週 | 6.52 | 4.12 | 6.13 | 9.38 | 7.97 | 6.89 |
| 2024/01/01週 | 8.69 | 4.93 | 6.24 | 9.29 | 7.61 | 6.70 |
| 2024/01/08週 | 8.07 | 3.96 | 7.57 | 8.88 | 6.46 | 6.82 |
| 2024/01/15週 | 9.86 | 4.40 | 8.67 | 9.29 | 6.01 | 6.74 |
| 2024/01/22週 | 9.73 | 4.07 | 10.05 | 9.48 | 5.44 | 6.46 |
| 2024/01/29週 | 8.65 | 5.15 | 9.84 | 9.27 | 5.01 | 6.61 |
df_shutu_pi |>
ggplot() + aes(x = 対象週, y = 平均出現店数量PI, color = 商品名, group = 商品名) +
geom_line() + geom_point() +
labs(title = "図6-15: 6種類のスナック菓子の出現店数量PIの推移") +
theme_bw(base_family = "HiraKakuPro-W3") +
# X軸のラベルを45度回転
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_fill_tableau(name = "Tableau 20")
出現店ベースの金額PIの表を出力します。
df_shutu_kin_pi <- df |>
group_by(対象週, 商品名) |>
summarise(
平均出現店金額PI = mean(出現店金額PI)
) |>
ungroup()`summarise()` has grouped output by '対象週'. You can override using the
`.groups` argument.
df_shutu_kin_pi |>
pivot_wider(names_from = 商品名, values_from = 平均出現店金額PI) |>
ungroup() |>
select(対象週, クリスマス限定, こだわり味, しびれ味, ピリ辛味, 激辛味, 定番味) |>
gt() |>
fmt_number(columns = 2:7, decimals = 2) |>
fmt_missing(columns = 2:7, missing_text = "") |>
gt_color_rows(2:7, domain = c(300, 3400), palette = "ggsci::blue_material") |>
tab_header(title = "表6-7: 6種類のスナック菓子の出現店金額PI") |>
tab_options(
heading.title.font.size = "normal",
table.font.size = "small"
) |>
gt_theme_pff()| 表6-7: 6種類のスナック菓子の出現店金額PI | ||||||
|---|---|---|---|---|---|---|
| 対象週 | クリスマス限定 | こだわり味 | しびれ味 | ピリ辛味 | 激辛味 | 定番味 |
| 2023/10/30週 | 1,151.99 | 892.54 | 1,386.00 | 2,873.65 | 1,032.63 | |
| 2023/11/06週 | 1,154.63 | 1,086.32 | 1,386.07 | 2,762.63 | 982.14 | |
| 2023/11/13週 | 1,135.17 | 986.49 | 1,340.89 | 3,304.22 | 1,001.08 | |
| 2023/11/20週 | 1,139.04 | 1,051.24 | 1,311.15 | 3,003.20 | 956.01 | |
| 2023/11/27週 | 1,316.08 | 892.59 | 1,416.93 | 2,146.58 | 1,007.71 | |
| 2023/12/04週 | 1,177.51 | 1,042.72 | 648.75 | 1,340.90 | 1,724.24 | 973.92 |
| 2023/12/11週 | 1,226.27 | 976.64 | 519.31 | 1,379.94 | 1,620.44 | 1,004.86 |
| 2023/12/18週 | 1,059.23 | 975.79 | 851.48 | 1,358.93 | 1,368.39 | 1,004.05 |
| 2023/12/25週 | 349.99 | 899.09 | 979.24 | 1,398.10 | 1,267.13 | 1,026.02 |
| 2024/01/01週 | 544.35 | 1,074.27 | 1,011.31 | 1,402.88 | 1,216.94 | 997.55 |
| 2024/01/08週 | 378.88 | 863.50 | 1,226.92 | 1,323.83 | 1,026.48 | 1,016.58 |
| 2024/01/15週 | 541.26 | 959.40 | 1,370.27 | 1,402.64 | 967.29 | 1,004.18 |
| 2024/01/22週 | 428.13 | 890.42 | 1,618.83 | 1,411.93 | 880.74 | 1,003.20 |
| 2024/01/29週 | 374.89 | 1,127.79 | 1,564.84 | 1,390.33 | 790.99 | 991.74 |
上の表をもとに折れ線グラフにします。
df_shutu_kin_pi |>
ggplot() + aes(x = 対象週, y = 平均出現店金額PI, color = 商品名, group = 商品名) +
geom_line() + geom_point() +
labs(title = "図6-17: 6種類のスナック菓子の出現店金額PIの推移") +
theme_bw(base_family = "HiraKakuPro-W3") +
# X軸のラベルを45度回転
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_fill_tableau(name = "Tableau 20")