All Articles
    Search by

    [tidyverse関数辞書] dplyr::distinct()の使い方

    はじめに

    この記事ではdplyrパッケージの関数である、distinct()について解説します!
    データフレームから重複を除去するdistinct()関数は、大規模なデータを取り扱うときに多用する関数です。
    今回の記事作成にあたって公式のドキュメントを読み込んできたのですが、意外とオプション指定で使い方の幅が広がるように感じました。知ってる人も是非最後までみていってください😎

    クイックリファレンス

    library(dplyr)
    
    # マニュアル
    distinct(データフレーム, ..., .keep_all = TRUE/FALSE)
    
    # パイプを使ったモダンな方法
    データフレーム %>%
      distinct(...)

    関数について

    dplyr::distinct()の概要

    {tidyverse}に含まれる、{dplyr}の関数の一つです。データフレーム(またはtibble)の重複行を削除する関数です。

    データフレームを使った テーブル変形では、同じ行データが重複して存在していると思わぬ結果を生む可能性があります。たとえば、グループごとの合計値を集計しようとしたときに、重複データがあると想定よりも合計値は大きくなってしまいます。

    重複が発生するのは主に以下の二ケースだと思います。

    • もともとのデータに問題がある
    • tidyverseのパイプ処理を繰り返すうちにどこから重複を発生させてしまっている

    前者の場合はdistinct()関数で修正するのが健全なやり方ですが、後者では付け焼刃的な修正になってしまうため、 重複を生む原因を探る方が先決です。

    Snitch

    Snitch

    パイプが10行くらいつながると意図せず重複行ができるあるある

    サンプルデータで単純な重複行の削除を試す

    おそらくこの用途が最も単純で、よく使う方法ではないかと思います。単純に重複する行を削除する目的で、以下のように使います。

    data %>% 
        distinct()

    以下のデータを例に、distinct()を使うメリットを見てみたいと思います。

    pacman::p_load(tidyverse, magrittr)
    house_price <- 
        read_tsv("./bengaluru_house_price.csv") 
    
    house_price
    
    ## # A tibble: 13,320 × 9
    ##    area_type  availability location size  society total_sqft  bath balcony price
    ##    <chr>      <chr>        <chr>    <chr> <chr>   <chr>      <dbl>   <dbl> <dbl>
    ##  1 Super bui… 19-Dec       Electro… 2 BHK Coomee  1056           2       1  39.1
    ##  2 Plot  Area Ready To Mo… Chikka … 4 Be… Theanmp 2600           5       3 120  
    ##  3 Built-up … Ready To Mo… Uttarah… 3 BHK <NA>    1440           2       3  62  
    ##  4 Super bui… Ready To Mo… Lingadh… 3 BHK Soiewre 1521           3       1  95  
    ##  5 Super bui… Ready To Mo… Kothanur 2 BHK <NA>    1200           2       1  51  
    ##  6 Super bui… Ready To Mo… Whitefi… 2 BHK DuenaTa 1170           2       1  38  
    ##  7 Super bui… 18-May       Old Air… 4 BHK Jaades  2732           4      NA 204  
    ##  8 Super bui… Ready To Mo… Rajaji … 4 BHK Brway G 3300           4      NA 600  
    ##  9 Super bui… Ready To Mo… Maratha… 3 BHK <NA>    1310           3       1  63.2
    ## 10 Plot  Area Ready To Mo… Gandhi … 6 Be… <NA>    1020           6      NA 370  
    ## # … with 13,310 more rows

    このデータはベンガルの住宅価格に関するものです。 tibble情報を見ることで、行数が13,320行あることが分かります。

    しかし、実はこのデータには全く同じデータの重複行があります😫

    蛇足: 重複している行の抽出

    全ての列について重複した行を抽出したいケースっていうのもレアですが、やるとしたら以下のように 全ての列でgroup_by()を行った後、tally()で集計し、n >= 2でフィルターする方法が良いでしょう。

    この操作の様に全列に関する重複行を取り出してみることで、データそのものに不具合があったのか・自分の操作がバグを生んだのか、といった分析をすることができます。

    house_price %>% 
        group_by_all() %>% 
        tally() %>% 
        filter(n > 1)
    
    # # A tibble: 381 × 10
    # # Groups:   area_type, availability, location, size, society, total_sqft, bath, balcony [332]
    #    area_type      availability  location                 size  society total_sqft  bath balcony price     n
    #    <chr>          <chr>         <chr>                    <chr> <chr>   <chr>      <dbl>   <dbl> <dbl> <int>
    #  1 Built-up  Area 18-Aug        Electronic City Phase II 2 BHK Sryalan 1000           2       1  28.9     2
    #  2 Built-up  Area 18-Dec        Electronic City          2 BHK NA      1025           2       1  29.6     2
    #  3 Built-up  Area 18-Dec        Electronic City          2 BHK NA      1065           2       1  30.8     2
    #  4 Built-up  Area 18-Dec        Electronic City          2 BHK NA      1125           2       1  32.5     2
    #  5 Built-up  Area 18-Dec        Electronic City          2 BHK NA      1130           2       1  32.6     2
    #  6 Built-up  Area 18-Dec        Electronic City          2 BHK NA      1165           2       1  33.6     2
    #  7 Built-up  Area 18-Dec        Electronic City          2 BHK NA      1200           2       1  34.6     2
    #  8 Built-up  Area 18-Dec        Electronic City          3 BHK NA      1320           2       1  38.1     2
    #  9 Built-up  Area 18-Dec        Electronic City          3 BHK NA      1400           2       1  40.4     2
    # 10 Built-up  Area Ready To Move 5th Phase JP Nagar       2 BHK NA      1256           2       1  62.8     2
    # # … with 371 more rows

    どうやら381個の重複があったようですね。

    group_by()とtally()を組み合わせる方法とは別に、count()を使う方法もあります。

    Snitch

    Snitch

    最近tidyverse再入門してみたら group_by() %>% tally()count() で置き換えられることを知りました

    count()関数を使う場合は、“全列をグループ変数にする”ためにacross()関数を使う必要があります。この辺はちょっとテクいので、group_by()とtally()の方が直感的には分かりやすいかも。

    重複を除去してみる

    では今回のdistinct()関数を使うとどうなるか見てみます。

    house_price <- read_tsv("./bengaluru_house_price.csv")
    
    house_price %>% 
        distinct()
    
    # # A tibble: 12,790 × 9
    #    area_type            availability  location                 size      society total_sqft  bath balcony price
    #    <chr>                <chr>         <chr>                    <chr>     <chr>   <chr>      <dbl>   <dbl> <dbl>
    #  1 Super built-up  Area 19-Dec        Electronic City Phase II 2 BHK     Coomee  1056           2       1  39.1
    #  2 Plot  Area           Ready To Move Chikka Tirupathi         4 Bedroom Theanmp 2600           5       3 120  
    #  3 Built-up  Area       Ready To Move Uttarahalli              3 BHK     NA      1440           2       3  62  
    #  4 Super built-up  Area Ready To Move Lingadheeranahalli       3 BHK     Soiewre 1521           3       1  95  
    #  5 Super built-up  Area Ready To Move Kothanur                 2 BHK     NA      1200           2       1  51  
    #  6 Super built-up  Area Ready To Move Whitefield               2 BHK     DuenaTa 1170           2       1  38  
    #  7 Super built-up  Area 18-May        Old Airport Road         4 BHK     Jaades  2732           4      NA 204  
    #  8 Super built-up  Area Ready To Move Rajaji Nagar             4 BHK     Brway G 3300           4      NA 600  
    #  9 Super built-up  Area Ready To Move Marathahalli             3 BHK     NA      1310           3       1  63.2
    # 10 Plot  Area           Ready To Move Gandhi Bazar             6 Bedroom NA      1020           6      NA 370  
    # # … with 12,780 more rows

    データが12790行に減っていますので、重複が削除できたであろうことが分かります。
    念のためgroup_by_all()とtally()を使って確認してみます。

    house_price %>% 
        distinct() %>% 
        group_by_all() %>% 
        tally() %>% 
        filter(n > 1)
    
    # A tibble: 0 × 10

    distinct()関数がきちんと重複を削除してくれたことを確認できました。

    今回のデータは13320行もありますので、手動で重複を探すなんてとてもじゃないけど無理です。
    このような操作をたった一行で実現できるので、distinct()関数は重宝されます。

    特定の列に注目した削除

    データフレームの特定の列だけに注目していて、組み合わせが何パターンあるかだけを知りたい場合には列名を指定することで最小パターンを取得することができます。

    先ほどのベンガル住宅価格データを使ってみてみましょう。

    area_typeやsocietyは連続値ではなく、いくつかの固定された値から選択されているように見えます。

    これらについて、どんな値が含まれているのか、distinct()関数に列名を引数として与えることで確認してみましょう。

    house_price %>% 
        distinct(area_type, society)
    
    # # A tibble: 3,184 × 2
    #    area_type            society
    #    <chr>                <chr>  
    #  1 Super built-up  Area Coomee 
    #  2 Plot  Area           Theanmp
    #  3 Built-up  Area       NA     
    #  4 Super built-up  Area Soiewre
    #  5 Super built-up  Area NA     
    #  6 Super built-up  Area DuenaTa
    #  7 Super built-up  Area Jaades 
    #  8 Super built-up  Area Brway G
    #  9 Plot  Area           NA     
    # 10 Plot  Area           Prrry M
    # # … with 3,174 more rows

    このように、3174パターンが絞り込めました。

    単純に一列に含まれる値をリストアップするのにも使えますね。

    house_price %>% 
        distinct(area_type)
    
    # # A tibble: 4 × 1
    #   area_type           
    #   <chr>               
    # 1 Super built-up  Area
    # 2 Plot  Area          
    # 3 Built-up  Area      
    # 4 Carpet  Area 

    実はこの記事を書くにあたってちゃんと調べるまで、私もこの使い方を使ったことがありませんでした。
    ちなみに、distinct()のオプションを知らなかった時によく行っていたのは以下の例です。

    #真似してはいけない例
    
    house_price %>% 
        .$area_type %>% 
        unique()
    
    # [1] "Super built-up  Area" "Plot  Area"           "Built-up  Area"       "Carpet  Area"

    特定の列に注目しつつ、全ての列を残す.keep_allオプション

    先ほどのように特定の列に注目したdistinct()を使いつつも、残りの列情報も知りたい場合に.keep_all()関数を使うこともできます。

    しかし、.keep_all()は全ての列を保持しますが、distinct()関数に与えた列以外は全て代表値が
    採用されていることに注意です。
    他の列を使用したデータフレーム変形がこの後に控えている場合を考えると、.keep_all()を
    使う出番は少なそうです。

    house_price %>% 
        distinct(area_type, .keep_all = TRUE)
    
    # # A tibble: 4 × 9
    #   area_type            availability  location                 size      society total_sqft  bath balcony price
    #   <chr>                <chr>         <chr>                    <chr>     <chr>   <chr>      <dbl>   <dbl> <dbl>
    # 1 Super built-up  Area 19-Dec        Electronic City Phase II 2 BHK     Coomee  1056           2       1  39.1
    # 2 Plot  Area           Ready To Move Chikka Tirupathi         4 Bedroom Theanmp 2600           5       3 120  
    # 3 Built-up  Area       Ready To Move Uttarahalli              3 BHK     NA      1440           2       3  62  
    # 4 Carpet  Area         Ready To Move Maruthi Sevanagar        2 BHK     SMikaay 950            2       2  47  

    まとめ

    以上、distinct()の使い方でした!
    オプションを活用する方法は見落とされがちですが、「特定の列に含まれる要素の取り出し」はイディオム的に覚える価値がある応用技だと思います。是非分からなくなったら辞書代わりにこの記事を参考にしてください~

    • dplyr::distinct()は重複行を削除する関数
    • 特にオプションを指定せずに使うことが一般的
    • 特定の列だけに注目して重複を削除することもできる
    • .keep_allオプションは扱いに注意が必要

    Published Apr 2, 2022

    © 2020-2024 Hiroyuki Odake