データサイエンス100本ノック 59~60


標準化、正規化
---

# 59　標準化
標準化とは平均が０、分散・標準偏差が１になるように変換することです。
Rでは標準化を行うためのライブラリが提供されてます。
<dl>
　<dt>用途 </dt>
    <code>scale(x, center = TRUE, scale = TRUE)</code>
  <dt>引数１</dt>
    <dd><code>center = TRUE</code>で各データから平均が引かれる </dd>
  <dt>引数２</dt>
    <dd><code>scale = TRUE</code>で各データが標準偏差で割られる</dd>
</dl>

:::note info
*売上金を顧客IDごとに合計して、その金額を標準化する問題です*
:::


>R-059: レシート明細データ（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、売上金額合計を平均0、標準偏差1に標準化して顧客ID、売上金額合計とともに10件表示せよ。標準化に使用する標準偏差は、分散の平方根、もしくは不偏分散の平方根のどちらでも良いものとする。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。


```R:59
# Rのscaleを使用しているため不変分散の平方根による標準偏差で標準化される
df_receipt %>% 
        filter(!grepl("^Z",customer_id)) %>%
        group_by(customer_id) %>%
        summarise(sum_amount = sum(amount), .groups = 'drop') %>%
        mutate(std_amount = scale(sum_amount, center = TRUE, scale = TRUE)) %>%
        slice(1:10)
```

# 60 正規化
正規化とは各データから「最小値」を引き、「最大値ー最小値」で割ることで、
データが0~1の範囲に収まるようにすることです。

<dl>
　<dt>用途 </dt>
    <code>scale(x, center = TRUE, scale = TRUE)</code>
  <dt>引数１</dt>
    <dd><code>center = min(x)</code>で各データから平均が引かれる </dd>
  <dt>引数２</dt>
    <dd><code>scale = max(x)-min(x)</code>で各データが標準偏差で割られる</dd>
</dl>

:::note info
*売上金を顧客IDごとに合計して、その金額を正規化する問題です*
:::

>R-060: レシート明細データ（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、売上金額合計を最小値0、最大値1に正規化して顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。

```R:060
df_receipt %>% 
    filter(!grepl("^Z", customer_id)) %>%
    group_by(customer_id) %>%
    summarise(sum_amount = sum(amount), .groups = 'drop') %>%
    mutate(
        scale_amount = scale(sum_amount, 
                             center = min(sum_amount),
                             scale = max(sum_amount) - min(sum_amount))) %>%
    slice(1:10)
```

### 参考

<code>scale</code>は<code>center</code>で指定した値を引き、<code>scale</code>で指定した値を割ることもできる

:::note info
<code>scale</code>関数の上記の問題以外での活用法です
:::

```R:scale
# ベクトルデータの作成
vector <- c(10, 20, 30, 40, 50)

# 平均100、標準偏差15でスケーリング
scaled_vector <- scale(vector, center = 100, scale = 15)

# 結果の表示
print(scaled_vector)
```

# 61 対数変換（常用対数）

データが小さい時はその範囲が拡大され、データが大きい時はその範囲が縮小されるため、対数化を行うことでデータを正規分布に近づけることができます。

:::note info
特徴量エンジニアリングとして最初に検討するのが対数変換です
それによって、ロングテイルデータを正規分布に近づけることができます
[数値データの取り扱いについて（外部ページ）](https://uribo.github.io/practical-ds/02/numeric.html)
:::



<d1>
<dt> Usage </dt>
    <code>log(x, base = exp(1))</code>

<dt>Arguments</dt>
<dt>x</dt> 数値（か複素数）

<dt>base</dt>	
対数を計算する際の底
<dd>デフォルトはネイピア数e
</d1>

>R-061: レシート明細データ（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、売上金額合計を常用対数化（底10）して顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。

```R:061
df_receipt %>%
    filter(!grepl("^Z",customer_id)) %>%
    group_by(customer_id) %>%
    summarise(sum_amount = sum(amount), .groups = "drop") %>%
    mutate(log_amount = log(sum_amount + 0.5), base = 10) %>%
    slice(1:5)
```

# 62　対数変換（自然対数）

:::note info
先ほどの常用対数を自然対数にしたバージョンです
:::


<d1>
<dt> Usage </dt>
    <code>log(x, base = exp(1))</code>

<dt>Arguments</dt>
<dt>x</dt> 数値（か複素数）

<dt>base</dt>	
対数を計算する際の底
<dd>デフォルトはネイピア数e
</d1>

>R-062: レシート明細データ（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、売上金額合計を自然対数化（底e）して顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。

```R:062
df_receipt %>% 
    filter(!grepl("^Z",customer_id)) %>%
    group_by(customer_id) %>%
    summarise(sum_amount = sum(amount), .groups = "drop") %>%
    mutate(log_amount = log(sum_amount + 0.5)) %>%
    slice(1:5)
```

# 63 四則演算
:::note info
引き算、割り算をやります
:::
>R-063: 商品データ（df_product）の単価（unit_price）と原価（unit_cost）から各商品の利益額を算出し、結果を10件表示せよ。

```R:063
df_product %>%
        mutate(unit_profit = unit_price - unit_cost) %>%
        select(product_cd,unit_price,unit_cost,unit_profit) %>%
        slice(1:5)
```

# 64 四則演算
>R-064: 商品データ（df_product）の単価（unit_price）と原価（unit_cost）から、各商品の利益率の全体平均を算出せよ。ただし、単価と原価には欠損が生じていることに注意せよ。

```R:064
df_product %>% 
        mutate(unit_profit_rate = (unit_price - unit_cost)/ unit_price *100) %>%
        summarise(average_profit_rate = mean(unit_profit_rate, na.rm = TRUE))
```

# 69 集計結果から割合を求める

:::note info
この問題では、以下が要件となっている。
①レシート明細データと商品データを結合する
②全商品の売上金額合計とカテゴリ大区分コード07の売上金額合計を計算
③”07/全商品”を求める
④結果を10件表示する
:::

>R-069: レシート明細データ（df_receipt）と商品データ（df_product）を結合し、顧客毎に全商品の売上金額合計と、カテゴリ大区分コード（category_major_cd）が"07"（瓶詰缶詰）の売上金額合計を計算の上、両者の比率を求めよ。抽出対象はカテゴリ大区分コード"07"（瓶詰缶詰）の売上実績がある顧客のみとし、結果を10件表示せよ。

```R:069　回答
df_receipt %>% 
        left_join(df_product,by = "product_cd") %>%
        group_by(customer_id) %>%
        summarise(sum_percustomer = sum(amount), .groups = "drop")%>%
        left_join(df_receipt, by = "customer_id") %>%
        left_join(df_product,by = "product_cd") %>%
        filter(category_major_cd == "07") %>%
        group_by(customer_id) %>%
        summarise(sum_07 = sum(amount),sum_percustomer = max(sum_percustomer),.groups = "drop") %>%
        mutate(sales_rate = sum_07/sum_percustomer) %>%
        arrange(desc(sum_percustomer)) %>%
        slice(1:5)
```

```R:069　教科書解答
#　df_tmp_1では顧客別の全売上金額合計を算出する
df_tmp_1 <- df_receipt　%>%
        group_by(customer_id) %>%
        summarize(sum_all = sum(amount))

# df_tmp_2ではカテゴリ大区分コード07の売上金額合計を算出する
df_tmp_2 <- inner_join(df_receipt,
                       df_product[c("product_cd","category_major_cd")],
                       by = "product_cd") %>%
            # カテゴリ大区分コード別で集計したいためfilterで抽出する
            filter(category_major_cd == "07")　%>%
            group_by(customer_id) %>%
            summarize(sum_07 = sum(amount),.groups = "drop")

# 最後にで売上合計金額の２つのデータを結合して加える
inner_join(df_tmp_1,df_tmp_2,by = "customer_id") %>%
            mutate(sum_rate = sum_07/sum_all*100) %>%
            arrange(desc(sum_07)) %>%
            slice(1:5)
```

# 070 顧客
初級者にとってはかなり苦戦させられた。
というのも、どのような関数と引数で日付型の変数に変換するのか？
経過日数をどのようにすれば計算できるのかが慣れていないからだ。
さっそく、取り組もう

:::note info
この問題では以下が要件となっている。
①レシート明細データの売上日から顧客データの会員申込日の経過日数を計算
    
    1-1 レシート明細データと顧客データを結合する
    1-2 売上日と会員申込日を計算できるように揃える
②顧客ID,売上日,会員申込日,経過日数を表示
:::

>R-070: レシート明細データ（df_receipt）の売上日（sales_ymd）に対し、顧客データ（df_customer）の会員申込日（application_date）からの経過日数を計算し、顧客ID（customer_id）、売上日、会員申込日とともに10件表示せよ（sales_ymdは数値、application_dateは文字列でデータを保持している点に注意）

```R:070
df_merged <- inner_join(df_receipt,df_customer,by = "customer_id")

df_merged <- df_merged %>% 
            mutate(sales_ymd = as.Date(as.character(sales_ymd),format = "%Y%m%d"),
                    application_date = as.Date(application_date,format = "%Y%m%d")) %>%
            mutate(passed_date = as.numeric(difftime(application_date,sales_ymd,units = "days")))%>%
            select(customer_id,application_date,sales_ymd,passed_date) %>%
            slice(1:5)
```

分析の引き出しになったという所感だ。
気が向いたら随時更新する。

以上