トラック
/
R
R
/
シラバス
/
データフレーム
デー

データフレーム の R

1個の演習

データフレームについて

シラバスのほかの部分では、さまざまな特徴をもつデータ型を見てきました。

  • アトミックなベクトルは1次元で、型が均一です。
  • 配列は1次元で、要素は異なる型にできます。
  • 行列と配列は多次元で、均一です。

このコンセプトでは、多次元で異なる型が混在するデータを保存する方法を見ていきます。 実際のところ、現実世界のデータの_ほとんど_はこの形をしているので、ここからRが実務で(主に)どのように使われているかの核心に入っていきます。

データフレームの種類

ここ数十年のあいだに、Rは表形式のデータを扱うためのデータ型をいくつも追加してきました。

このシラバスでは主にtibbleを扱いますが、いくつかの代替について知っておくと役に立ちます。

data.table

tibble(後述)とdata.tableはどちらも、Base Rの従来のdata.frameを改善しようとした、評価の高い試みです。 どちらが「優れているか」については、当然ながら多くの議論があります。 次の点については、(単純すぎるという批判はあるにせよ)ある程度の合意があるかもしれません。

  • tibbleは主に使いやすさと、Tidyverseエコシステムとの統合を重視して最適化されています。
  • data.tableは主に、とりわけ非常に大きなデータセットを扱うときの生のパワーとスケーラビリティを重視して最適化されています。

いずれにせよ、data.tableはExercismでは利用できないので、ここでは網羅性のために触れておくだけです。

data.frame

Base Rでは、data.frameは同じ長さのvectorsのlistです。 これは、データの長方形の表と考えることができます。各列は均一ですが、各行には(たいていは)異なる型のデータが入ります。

これを示す例を見てみましょう。

# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)

# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#>   languages created has.syllabus
#> 1   Fortran    1957        FALSE
#> 2         R    1993         TRUE
#> 3    Python    1991         TRUE
#> 4     Julia    2012         TRUE

# look at the structure
str(df)
#> 'data.frame':	4 obs. of  3 variables:
 #> $ languages   : chr  "Fortran" "R" "Python" "Julia"
 #> $ created     : num  1957 1993 1991 2012
 #> $ has.syllabus: logi  FALSE TRUE TRUE TRUE

文字列の列、数値の列、真偽値の列があります。 大規模になったときにも、現実世界の多くのデータの集まりを直感的に表せる方法です。

tibble

data.frameの設計は_古い_ものです。

数十年にわたる経験と、Rの使われ方の変化が再設計につながり、Tidyverseに近代化された選択肢としてtibbleが生まれました。

Base Rと比べて、tibbleには次の特徴があります。

  • よくある問題を減らすための、異なるデフォルトの挙動。
  • 入力時にデータ型を無理に変換しようとしない。
  • より多く、よりわかりやすいエラーメッセージ。
  • 異なる、たいていはより見やすい表示形式。

要するに、tibbleは「やることが少なく、文句が多め」を目指しており、「怠惰で不機嫌」とも表現されます。

これらの型はたいてい互換性があります。data.frameを受け入れる関数はtibbleも受け入れ、_その逆も_同様です。

型の変換は簡単で、as_tibble(df)とas.data.frame(tbl)を使います。

新しい作業には、tibbleを使うとより堅牢なコードを書くのに役立つでしょう。 ただし、Rの世界にはレガシーなコードとデータが大量にあるため、data.frameはこれからも長く一般的であり続けるでしょう。

# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE      
  
str(tbl)
#> tibble [4 × 3] (S3: tbl_df/tbl/data.frame)
#>  $ languages   : chr [1:4] "Fortran" "R" "Python" "Julia"
#>  $ created     : num [1:4] 1957 1993 1991 2012
#>  $ has.syllabus: logi [1:4] FALSE TRUE TRUE TRUE

デフォルトの表示形式に注目してください。次元を示すコメント行が自動的に表示され、列の型も表示されます。

tibbleを扱う

tibbleはTidyverseの中核なので、library(tibble)かlibrary(tidyverse)で読み込みます。

ドキュメントはTidyverseらしいスタイルで、かなり充実しています。

tibbleを作る

もっとも簡単には、data.frame()と同じように、tibble()関数で列ベクトルをまとめることができます。 その例は前のセクションで示しました。

行単位で値を入力するほうが便利な場合は、tribble()関数を使います。

tbl_r <- tribble(
  # column names marked with tilde prefix
  ~languages, ~created, ~has.syllabus,
  "Fortran", 1957, FALSE,
  "R", 1993, TRUE,
  "Python", 1991, TRUE,
  "Julia", 2012, TRUE
)

tbl_r
# A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE    

実際には、tibbleを作る方法は何十通りもあります。さまざまなTidyverse関数が、デフォルトの出力形式としてtibbleを返すからです。 これについては今後のコンセプトで改めて見ていきます。

tibbleを操作する

関数型プログラミングのコンセプトでは、ベクトルと配列(1次元のデータ構造)を操作するpurrrライブラリを扱いました。

データフレーム(従来のものでもtibbleでも)に対しては、対応するライブラリとしてdplyrを使います。

dplyrは以前、Switchのコンセプトで紹介しました。 そこではいくつかのユーティリティ関数を使っただけですが、ここからこの大きなライブラリの残りの部分も見ていきましょう。

サブセット化

データフレームは、tibbleも含めて列ベクトルの配列とみなせるので、配列のインデックスで指定した列を取り出せます。

tbl
# A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE    

tbl$created
#> [1] 1957 1993 1991 2012

データフレームは行列形式のインデックスでも参照できます。

tbl[c(2, 4), 1:2]
  # A tibble: 2 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 R            1993
#> 2 Julia        2012

Tidyverseエコシステムを備えた現代のRでは、dplyrの関数のほうが一般に柔軟で便利なので、このコンセプトの残りではそちらに焦点を当てます。

列単位の操作

pull()で、名前か連番(負の数なら右から数えます)を指定して1つの列を取り出します。

tbl |> pull(created)
#> [1] 1957 1993 1991 2012

これはtbl$createdと同じ結果ですが、パイプラインで使いやすい関数を使っています。

複数の列を取り出すには、select()が適切です。これはとても柔軟です。 列の名前や型の性質に基づいて、列を取得(または除外)できます。

# Range with position and/or name
tbl |> select(1:created)
  # A tibble: 4 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 Fortran      1957
#> 2 R            1993
#> 3 Python       1991
#> 4 Julia        2012

# Exclude a column
tbl |> select(!created)
  # A tibble: 4 × 2
#>   languages has.syllabus
#>   <chr>     <lgl>       
#> 1 Fortran   FALSE       
#> 2 R         TRUE        
#> 3 Python    TRUE        
#> 4 Julia     TRUE        

# Use type of column
tbl |> select(where(is.numeric))
  # A tibble: 4 × 1
#>   created
#>     <dbl>
#> 1    1957
#> 2    1993
#> 3    1991
#> 4    2012

複数の条件は、真偽値演算子&、|、!(and、or、not)を使って指定できます。

_有効なRの識別子_である列名は、select()の中では引用符で囲む必要はありません。 無効な名前(スペースを含むものなど)はバッククォートで囲めますが、名前を変更したほうがよいかもしれません。

select()関数は、列名を選ぶためのさまざまなヘルパー関数と組み合わせて使えます。starts_with、contains、num_rangeなどです。 matchesでは正規表現による完全一致が使えます。 詳しくはドキュメントを参照してください。

言語だけのおもちゃのデータフレームでは、これほどの機能はばかげて思えます。 幸い、dplyrにはstarwarstibbleが含まれているので、練習にもっと大きなものを使えます。

# limit display to top 3 rows of non-list columns
starwars |> 
  select(!where(is.list)) |> 
  head(3)
  # A tibble: 3 × 11
#>   name           height  mass hair_color skin_color  eye_color birth_year sex   gender    homeworld species
#>   <chr>           <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>     <chr>     <chr>  
#> 1 Luke Skywalker    172    77 blond      fair        blue              19 male  masculine Tatooine  Human  
#> 2 C-3PO             167    75 NA         gold        yellow           112 none  masculine Tatooine  Droid  
#> 3 R2-D2              96    32 NA         white, blue red               33 none  masculine Naboo     Droid  

# pick a subset of columns
starwars |> 
  select(name | ends_with("color")) |> 
  head(5)
  # A tibble: 5 × 4
#>   name           hair_color skin_color  eye_color
#>   <chr>          <chr>      <chr>       <chr>    
#> 1 Luke Skywalker blond      fair        blue     
#> 2 C-3PO          NA         gold        yellow   
#> 3 R2-D2          NA         white, blue red      
#> 4 Darth Vader    none       white       yellow   
#> 5 Leia Organa    brown      light       brown    

行単位の操作

Note

明らかに、dplyrは名前で列を選ぶ強力な方法を提供しています。

行名でも同じようなことができるのでしょうか?

いいえ! 従来のRのデータフレームには行名を付けられますが、(バグと性能問題の歴史を経て)tibblesでは行名は_許されていません_。

名前を付けたい場合は、<chr>の列(通常は1列目)に入れて、ほかの列と同じように使います。 as_tibble()のようなインポート関数は、名前付きの行をもつデータを取り込むときに、これを自動的に作ります。

この行名の制限が妙に厳しく思えるなら、ほとんどの大規模データベースシステムも同じようにテーブルを扱うことを思い出してください。Oracle、SQL Server、PostgreSQL、MySQLなどです。

filter()で条件に一致する行を取り出し、filter_out()で除外します。

starwars |> 
  select(name:mass) |> 
  filter(between(height, 150, 165) & !is.na(mass))
  # A tibble: 4 × 3
#>   name               height  mass
#>   <chr>               <int> <dbl>
#> 1 Leia Organa           150    49
#> 2 Beru Whitesun Lars    165    75
#> 3 Nien Nunb             160    68
#> 4 Ben Quadinaros        163    65

フィルターの条件はいくらでも複雑にできますが、常に行の_中身_に基づきます。

行_番号_がわかっている場合は、さまざまなslice()関数が使えます。

starwars |> 
  select(name | homeworld) |> 
  slice(20:25)
  # A tibble: 6 × 2
#>   name             homeworld
#>   <chr>            <chr>    
#> 1 Palpatine        Naboo    
#> 2 Boba Fett        Kamino   
#> 3 IG-88            NA       
#> 4 Bossk            Trandosha
#> 5 Lando Calrissian Socorro  
#> 6 Lobot            Bespin   

# random sample of rows
starwars |> 
  select(name | homeworld) |> 
  slice_sample(n = 4)
  # A tibble: 4 × 2
#>   name            homeworld
#>   <chr>           <chr>    
#> 1 Shaak Ti        Shili    
#> 2 Luminara Unduli Mirial   
#> 3 Grievous        Kalee    
#> 4 Palpatine       Naboo    

重複した行を削除するには、distinct()を使います。

tibbleを変更する

まず注意点として、変更時にコピーがデフォルトなので、元のtibbleはたいてい変更されません。

ほとんどの変更は列単位で行います。

列名はrename(newname = oldname)で変更できます。関数を適用するならrename_with()です。 典型的な使い方は、取り込んだ名前から空白を削除し、関連する名前の形式をそろえて、Rで扱いやすくする整理です。

rename()の中の構文に注目してください。 列oldnameの_中身_が名前newnameに_結び付けられる_ので、この順番になります。

列の順序はrelocate()で変更できます。 指定した列はデフォルトで左端に移動しますが、.beforeか.after引数を使えばより細かく位置を指定できます。

sw <- starwars |> select(name:species) |> slice(1:4)
sw
  # A tibble: 4 × 11
#>   name           height  mass hair_color skin_color  eye_color birth_year sex   gender    homeworld species
#>   <chr>           <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>     <chr>     <chr>  
#> 1 Luke Skywalker    172    77 blond      fair        blue            19   male  masculine Tatooine  Human  
#> 2 C-3PO             167    75 NA         gold        yellow         112   none  masculine Tatooine  Droid  
#> 3 R2-D2              96    32 NA         white, blue red             33   none  masculine Naboo     Droid  
#> 4 Darth Vader       202   136 none       white       yellow          41.9 male  masculine Tatooine  Human  

sw |> relocate(c(species, homeworld), .after = name)
  # A tibble: 4 × 11
#>   name           species homeworld height  mass hair_color skin_color  eye_color birth_year sex   gender   
#>   <chr>          <chr>   <chr>      <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>    
#> 1 Luke Skywalker Human   Tatooine     172    77 blond      fair        blue            19   male  masculine
#> 2 C-3PO          Droid   Tatooine     167    75 NA         gold        yellow         112   none  masculine
#> 3 R2-D2          Droid   Naboo         96    32 NA         white, blue red             33   none  masculine
#> 4 Darth Vader    Human   Tatooine     202   136 none       white       yellow          41.9 male  masculine

もっと大きな変更には、mutate()が使えます。

  • 既存の列を関数として使って新しい列を作る。
  • 同じ名前の新しい列を作ることで、既存の列を置き換える。
  • 値をNULLに設定して、列を削除する。

明らかに、mutate()は強力で、ときに混乱を招き、変更時にコピーしてくれることに感謝したくなる理由です。

スター・ウォーズの登場人物のボディマス指数を気にする理由は特にありませんが、念のため。

starwars |> 
  select(c(name, species, height, mass)) |> 
  mutate(BMI = mass / (height / 100)^2) |> 
  head(4)
  # A tibble: 4 × 5
#>   name           species height  mass   BMI
#>   <chr>          <chr>    <int> <dbl> <dbl>
#> 1 Luke Skywalker Human      172    77  26.0
#> 2 C-3PO          Droid      167    75  26.9
#> 3 R2-D2          Droid       96    32  34.7
#> 4 Darth Vader    Human      202   136  33.3

mutate()のような関数で一部の列だけを操作したいときは、上の例のselect() |> mutate()の順序が1つの選択肢です。 結果には選んだ列だけが残ります。

あるいは、mutate()の呼び出しの_中で_pick()を使うと便利なことがあります。

starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
When you want to operate on a subset of the columns with functions such as `mutate()`, the `select() |> mutate()` sequence in the above example is one option.
Only the selected columns will be in the result.

Alternatively, it can be convenient to use `pick()` _within_ the `mutate()` call:

```R
starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
    # A tibble: 4 × 15
#>   name         height  mass hair_color skin_color eye_color birth_year sex   gender homeworld species films vehicles
#>   <chr>         <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr>  <chr>     <chr>   <lis> <list>  
#> 1 Luke Skywal…    172    77 blond      fair       blue            19   male  mascu… Tatooine  Human   <chr> <chr>   
#> 2 C-3PO           167    75 NA         gold       yellow         112   none  mascu… Tatooine  Droid   <chr> <chr>   
#> 3 R2-D2            96    32 NA         white, bl… red             33   none  mascu… Naboo     Droid   <chr> <chr>   
#> 4 Darth Vader     202   136 none       white      yellow          41.9 male  mascu… Tatooine  Human   <chr> <chr>   
   # ℹ 2 more variables: starships <list>, BMI <dbl>

pickした列だけが変更に使われますが、返ってくるのはすべての列です。

行単位の操作は、1つのtibbleを変更する用途ではあまり一般的ではありません(複数のtibbleの結合は後のコンセプトで扱います)。

1つ例外があります。arrange()は、1つ以上の列の値で行を並べ替えます。

tbl
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE  

tbl |> arrange(languages)
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 Julia        2012 TRUE        
#> 3 Python       1991 TRUE        
#> 4 R            1993 TRUE     

まとめ

従来のものでもtibbleでも、データフレームは現代のRの典型的な使われ方の中心にあります。

Tidyverseの関数のほとんど(dplyrだけではありません)は、tibbleを入力として受け取ったり、出力として作ったりします。

このコンセプトでは簡単な紹介だけを行い、可能なことの表面をかすめたにすぎません。

今後のコンセプトでは、データフレームのさらにいくつかの側面を(Exercismの技術的な制約の範囲内で)扱います。

GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

データフレームを学習する