본문 바로가기
R

[dplyr] select, filter, mutate, summarise로 데이터 다루기

by phyzik 2023. 7. 26.

dplyr는 R에서 데이터를 다루기 위해 필수적으로 익혀야 할 패키지입니다. 데이터를 구성하는 row와 colum 들을 자유롭게 접근하기 위한 다양한 함수들이 있습니다. 우선, R에서 데이터를 구성하는 타입은 기본적으로 data.frame입니다. matrix 형태로도 데이터를 다룰 수 있지만, dplyr를 적용하기 위해서는 data.frame 형태여야 합니다. data.frmae은 row와 column으로 구성 되어 있고, 각 column의 타입은 달라도 상관 없습니다. 

 

1. data

먼저, 필요한 패키지를 불러오고, 사용할 데이터를 탐색합니다. 
사용할 데이터는 MLDataR 패키지의 heartdisease입니다. MLDataR은 머신러닝용 데이터를 모아놓은 패키지입니다. 패키지 로드 후, dplyr의 glimpse() 함수를 적용하여 데이터의 row와 column 및 속성들을 대략적으로 파악합니다. 해당예제에서는 heartdisease데이터에서 10개의 행만 추출해서 사용해보도록 하겠습니다. 

 

# 패키지 로드 
library(dplyr) 
library(MLDataR)

MLDataR::heartdisease

sample = heartdisease[c(726,732:740),]
glimpse(sample)
Show in New Window
Rows: 10
Columns: 10
$ Age              <dbl> 55, 46, 56, 66, 56, 49, 54, 57, 65, 54
$ Sex              <chr> "F", "M", "F", "F", "M", "M", "M", "M", "~
$ RestingBP        <dbl> 180, 120, 200, 150, 130, 120, 122, 152, 1~
$ Cholesterol      <dbl> 327, 249, 288, 226, 283, 188, 286, 274, 3~
$ FastingBS        <dbl> 0, 0, 1, 0, 1, 0, 0, 0, 0, 0
$ RestingECG       <chr> "ST", "LVH", "LVH", "Normal", "LVH", "Nor~
$ MaxHR            <dbl> 117, 144, 133, 114, 103, 139, 116, 88, 15~
$ Angina           <chr> "Y", "N", "Y", "N", "Y", "N", "Y", "Y", "~
$ HeartPeakReading <dbl> 3.4, 0.8, 4.0, 2.6, 1.6, 2.0, 3.2, 1.2, 0~
$ HeartDisease     <dbl> 1, 1, 1, 0, 1, 1, 1, 1, 0, 0

2. pipe 연산자 

데이터를 다루기에 앞서 pipe연산자에 대한 언급이 필요합니다. 파이프 연산자(pipe operator)는 "magrittr"이라는 패키지에 포함된 함수이지만, dplyr 로드시 magrittr에서 파이프 연산자를 불러오게 되어 magrittr를 로드 하지 않아도 사용 할 수 있습니다. pipe 연산자란 data.frame과 함수, 함수와 함수를 연결해주는 역할을 하며, "%>%"로 표시합니다. dplyr의 filter함수를 시작으로 pipe 연산자에 대한 내용도 알아보겠습니다. 

 

3. select 

select는 column을 선택하는 함수 입니다. 많은 상황에서 column의 이름을 알고 특정 column을 추출 하게 되지만, 상황에 따라 특정단어로 시작하거나, 끝나거나, 포함된 column을 추출해야 하는 경우도 있습니다.  colnames()함수로 sample의 column 이름을 확인해보겠습니다.

colnames(sample)
 [1] "Age"              "Sex"              "RestingBP"       
 [4] "Cholesterol"      "FastingBS"        "RestingECG"      
 [7] "MaxHR"            "Angina"           "HeartPeakReading"
[10] "HeartDisease"

 

"Age", "Sex", "RestingBP"를 추출해보겠습니다. 

sample %>% select(Age, Sex, RestingBP)
# A tibble: 10 x 3
     Age Sex   RestingBP
   <dbl> <chr>    <dbl>
 1    55  F        180
 2    46  M        120
 3    56  F        200
 4    66  F        150
 5    56  M        130
 6    49  M        120
 7    54  M        122
 8    57  M        152
 9    65  F        160
10    54  M        125

 

다음, column이름 중 특정단어로 시작하는 column을 추출 할 수 있습니다.  "A"로 시작하는 column을 추출해보겠습니다. 

sample %>% select(starts_with("A"))
# A tibble: 10 x 2
     Age Angina
   <dbl> <chr> 
 1    55  Y     
 2    46  N     
 3    56  Y     
 4    66  N     
 5    56  Y     
 6    49  N     
 7    54  Y     
 8    57  Y     
 9    65  N     
10    54  N

 

또, column이름 중 특정단어로 끝나는 column을 추출 할 수 있습니다.  

sample %>% select(ends_with("g"))
# A tibble: 10 x 2
   RestingECG HeartPeakReading
   <chr>                 <dbl>
 1 ST                      3.4
 2 LVH                     0.8
 3 LVH                     4  
 4 Normal                  2.6
 5 LVH                     1.6
 6 Normal                  2  
 7 LVH                     3.2
 8 Normal                  1.2
 9 LVH                     0.8
10 LVH                     0.5

 

contains()함수를 사용하면 특정단어가 포함된 coulmn을 추출 할 수도 있습니다. 아래 예제는, "l"을 포함하는 column을 추출하는 코드 입니다. 

sample %>% select(contains("l"))
# A tibble: 10 x 1
   Cholesterol
         <dbl>
 1         327
 2         249
 3         288
 4         226
 5         283
 6         188
 7         286
 8         274
 9         360
10         273

 

matches()함수도 contains()함수와 마찬가지로 특정단어를 포함한 열을 추출할 수 있습니다. matches()함수와 contains()함수의 차이는, matches()함수는 복수의 조건을 입력 할 수 있습니다. 

sample %>% select(matches("l")) # "l"이 포함된 column 
# A tibble: 10 x 1
   Cholesterol
         <dbl>
 1         327
 2         249
 3         288
 4         226
 5         283
 6         188
 7         286
 8         274
 9         360
10         273
sample %>% select(matches("l|p")) # "l" 또는 "p"가 포함된 column 
# A tibble: 10 x 3
   RestingBP Cholesterol HeartPeakReading
       <dbl>       <dbl>            <dbl>
 1       180         327              3.4
 2       120         249              0.8
 3       200         288              4  
 4       150         226              2.6
 5       130         283              1.6
 6       120         188              2  
 7       122         286              3.2
 8       152         274              1.2
 9       160         360              0.8
10       125         273              0.5

4. filter 

filter는 column 조건에 맞는 row를 추출하는 함수입니다. heatrdisease에서 추출한 sample 데이터는 10개의 row와 10개의 column으로 되어 있습니다. 10개의 column중 RestingECG의 데이터값을 살펴보겠습니다. unique()함수는 column이 가진 값들 중 중복을 제외한 고유한 값을 보여줍니다. 

unique(sample$RestingECG)
[1] "ST"     "LVH"    "Normal"

"Normal", "ST", "LVH"로 되어 있는 것을 확인 할 수 있습니다. 이 중, "LVH"값만 추출해보겠습니다. 

 

# 1. pipe operator
sample %>% filter(RestingECG == "LVH")
# A tibble: 6 x 10
    Age Sex   RestingBP Cholesterol FastingBS RestingECG MaxHR
  <dbl> <chr>     <dbl>       <dbl>     <dbl> <chr>      <dbl>
1    46 M           120         249         0 LVH          144
2    56 F           200         288         1 LVH          133
3    56 M           130         283         1 LVH          103
4    54 M           122         286         0 LVH          116
5    65 F           160         360         0 LVH          151
6    54 M           125         273         0 LVH          152
# i 3 more variables: Angina <chr>, HeartPeakReading <dbl>,
#   HeartDisease <dbl>

# 2. basic
filter(sample, RestingECG == "LVH")
# A tibble: 6 x 10
    Age Sex   RestingBP Cholesterol FastingBS RestingECG MaxHR
  <dbl> <chr>     <dbl>       <dbl>     <dbl> <chr>      <dbl>
1    46 M           120         249         0 LVH          144
2    56 F           200         288         1 LVH          133
3    56 M           130         283         1 LVH          103
4    54 M           122         286         0 LVH          116
5    65 F           160         360         0 LVH          151
6    54 M           125         273         0 LVH          152
# i 3 more variables: Angina <chr>, HeartPeakReading <dbl>,
#   HeartDisease <dbl>

 

filter()를 사용하면 RestingECG 컬럼에서 "LVH"값을 가지는 row를 모두 추출 할 수 있습니다. 1번 방법은 파이프연산자를 사용한 코드 입니다. 파이프 연산자는 "앞 %>% 뒤"로 볼 때, 앞에 뒤의 명령어를 적용하라는 의미입니다. 파이프연산자를 사용하면 R에서 좀 더 깔끔하고 간결한 쉬운 코드를 작성 할 수 있습니다. 2번 방법은 파이프 연산자를 사용하지 않은 코드 입니다. filter() 함수안에 data.frame과 조건식을 입력하면 되는데, 함수를 여러개 사용하고, 조건식이 많아 질 수록 코드 해석이 어려울 수 있습니다. 

summary()함수를 사용하면 column들의 데이터의 분포를 대략적으로 파악 할 수 있습니다. summary()를 통해 알 수 있는 값은 최소값(Min), 1st Qu(1사분위수), Median(중위수), Mean(평균값), 3rd Qu(3사분위수), Max(최대값)입니다. 

summary(sample)
      Age            Sex              RestingBP      Cholesterol      FastingBS    RestingECG            MaxHR          Angina          HeartPeakReading  HeartDisease 
 Min.   :46.00   Length:10          Min.   :120.0   Min.   :188.0   Min.   :0.0   Length:10          Min.   : 88.0   Length:10          Min.   :0.50     Min.   :0.00  
 1st Qu.:54.00   Class :character   1st Qu.:122.8   1st Qu.:255.0   1st Qu.:0.0   Class :character   1st Qu.:114.5   Class :character   1st Qu.:0.90     1st Qu.:0.25  
 Median :55.50   Mode  :character   Median :140.0   Median :278.5   Median :0.0   Mode  :character   Median :125.0   Mode  :character   Median :1.80     Median :1.00  
 Mean   :55.80                      Mean   :145.9   Mean   :275.4   Mean   :0.2                      Mean   :125.7                      Mean   :2.01     Mean   :0.70  
 3rd Qu.:56.75                      3rd Qu.:158.0   3rd Qu.:287.5   3rd Qu.:0.0                      3rd Qu.:142.8                      3rd Qu.:3.05     3rd Qu.:1.00  
 Max.   :66.00                      Max.   :200.0   Max.   :360.0   Max.   :1.0                      Max.   :152.0                      Max.   :4.00     Max.   :1.00

 

Cholesterol의 최소값(Min)은 188, 최대값(Max)은 360인것을 알 수 있습니다. 그럼, RestingECG는 "LVH"이고, Cholesterol은 200 이상인 row를 추출해보겠습니다. filter() 안에 RestingECG와 Cholesterol의 조건을 넣어주면 됩니다. 

sample %>% filter(RestingECG == "LVH", Cholesterol > 200)
# A tibble: 6 x 10
    Age Sex   RestingBP Cholesterol FastingBS RestingECG MaxHR Angina HeartPeakReading HeartDisease
  <dbl> <chr>     <dbl>       <dbl>     <dbl> <chr>      <dbl> <chr>             <dbl>        <dbl>
1    46 M           120         249         0 LVH          144 N                   0.8            1
2    56 F           200         288         1 LVH          133 Y                   4              1
3    56 M           130         283         1 LVH          103 Y                   1.6            1
4    54 M           122         286         0 LVH          116 Y                   3.2            1
5    65 F           160         360         0 LVH          151 N                   0.8            0
6    54 M           125         273         0 LVH          152 N                   0.5            0

 

만약, RestingECG의 "LVH"와 "Normal" 두 개 이상의 값과 일치하는 row를 찾기 위해서는 "%in%"를 사용하면 됩니다. unique()함수로 확인 했을 때 "LVH", "Normal"값만 들어 있는 것을 알 수 있습니다. 

result = sample %>% filter(RestingECG %in% c("LVH", "Normal"))
unique(result$RestingECG)
[1] "LVH"    "Normal"

5. mutate

mutate() 함수는 새로운 column을 활용해 새로운 변수를 만들어 기존의 data.frame에 추가해주는 함수 입니다. mean_ch라는 column이름으로 Cholestrol의 평균을 계산해 data.frame에 추가해보겠습니다. 

sample %>% mutate(mean_ch = mean(Cholesterol))
# A tibble: 10 x 11
     Age Sex   RestingBP Cholesterol FastingBS RestingECG MaxHR Angina HeartPeakReading HeartDisease mean_Ch
   <dbl> <chr>     <dbl>       <dbl>     <dbl> <chr>      <dbl> <chr>             <dbl>        <dbl>   <dbl>
 1    55 F           180         327         0 ST           117 Y                   3.4            1    275.
 2    46 M           120         249         0 LVH          144 N                   0.8            1    275.
 3    56 F           200         288         1 LVH          133 Y                   4              1    275.
 4    66 F           150         226         0 Normal       114 N                   2.6            0    275.
 5    56 M           130         283         1 LVH          103 Y                   1.6            1    275.
 6    49 M           120         188         0 Normal       139 N                   2              1    275.
 7    54 M           122         286         0 LVH          116 Y                   3.2            1    275.
 8    57 M           152         274         0 Normal        88 Y                   1.2            1    275.
 9    65 F           160         360         0 LVH          151 N                   0.8            0    275.
10    54 M           125         273         0 LVH          152 N                   0.5            0    275.

 

다음, "Age"와 "Cholesterol"를 더해서 "age_ch" column을 만들어 보겠습니다. 

sample %>% mutate(age_ch = Age + Cholesterol) %>% select(Age, Cholesterol, age_ch)
# A tibble: 10 x 3
     Age Cholesterol age_ch
   <dbl>       <dbl>  <dbl>
 1    55         327    382
 2    46         249    295
 3    56         288    344
 4    66         226    292
 5    56         283    339
 6    49         188    237
 7    54         286    340
 8    57         274    331
 9    65         360    425
10    54         273    327

mutate()는 ifelse와 함께 많이 사용 됩니다. 특정조건을 만족 할 때 값을 변경해주는 함수 입니다. 
"Angina" column에서 N 이면 0, Y이며 1로 값을 변경하고 그 값을 "Angina_n"으로 만들어 보겠습니다. 

sample %>% mutate(Angina_n = ifelse(Angina == "N", 0 , 1)) %>% select(Angina, Angina_n)
# A tibble: 10 x 2
   Angina Angina_n
   <chr>     <dbl>
 1 Y             1
 2 N             0
 3 Y             1
 4 N             0
 5 Y             1
 6 N             0
 7 Y             1
 8 Y             1
 9 N             0
10 N             0

5. summarise

summarise()함수는 column들의 기본적인 계산을 할 수 있게 해주는 함수 입니다. summarise()함수로는 주로 평균, 표준편차, 합계, column간의 연산 등의 계산을 할 때 주로 사용 합니다. 그럼 예시로, mean_age라는 column이름으로 Age의 평균을 계산해보겠습니다. 

sample %>% summarise(mean_age = mean(Age))
# A tibble: 1 x 1
  mean_age
     <dbl>
1     55.8

 

mutate()함수를 사용해서도 계산 할 수 있습니다. 하지만, 두 함수의 차이점은 mutate()는 계산한 결과를 기존의 data.frame에 붙여서 보여주고, summarise()는 계산결과만 단일값으로 보여줍니다. 

 

다음, Age와 Cholesterol을 더해보겠습니다. 

 sample %>% summarise(sum_age_ch = Age + Cholesterol)
# A tibble: 10 x 1
   sum_age_ch
        <dbl>
 1        382
 2        295
 3        344
 4        292
 5        339
 6        237
 7        340
 8        331
 9        425
10        327
Warning message:
Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0.
i Please use `reframe()` instead.
i When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

 

dplyr 버전 1.1.0이상에서는 행이 1개 이상 반환되는 결과에 대해서는 summarise() 대신 reframe()을 사용하라 되어 있습니다. reframe()은 하상 group화 되지않은 data.frame을 반환해주는 함수로 추후 다시 다루도록 하겠습니다. 

sample %>% reframe(sum_age_ch = Age + Cholesterol)
# A tibble: 10 x 1
   sum_age_ch
        <dbl>
 1        382
 2        295
 3        344
 4        292
 5        339
 6        237
 7        340
 8        331
 9        425
10        327

 

특히, summarise()는 group_by()함수와 함께 자주 사용 됩니다. group_by()는 column값들을 그룹을 지은 결과를 보여줍니다. "RestingECG"의 "LVH", "Normal", "ST" 별 Age의 평균값을 계산해 보겠습니다. 

sample %>% group_by(RestingECG) %>% summarise(mean_age = mean(Age))
# A tibble: 3 x 2
  RestingECG mean_age
  <chr>         <dbl>
1 LVH            55.2
2 Normal         57.3
3 ST             55

 

goroup_by()에 두 개 이상의 group이 들어가도 됩니다. 

sample %>% group_by(RestingECG, Angina) %>% summarise(mean_age = mean(Age))
`summarise()` has grouped output by 'RestingECG'. You can override using the `.groups` argument.
# A tibble: 5 x 3
# Groups:   RestingECG [3]
  RestingECG Angina mean_age
  <chr>      <chr>     <dbl>
1 LVH        N          55  
2 LVH        Y          55.3
3 Normal     N          57.5
4 Normal     Y          57  
5 ST         Y          55

평균 외에도 다양한 함수를 적용 할 수 있습니다. 평균, 표준편차, 중앙값, 최대값, 최소값을 한 번에 계산해보겠습니다. 

sample %>% group_by(RestingECG, Angina) %>% summarise(mean_age = mean(Age), 
                                                      sd_age = sd(Age), 
                                                      median_age = median(Age), 
                                                      max_age = max(Age), 
                                                      min_age = min(Age))
`summarise()` has grouped output by 'RestingECG'. You can override using the `.groups` argument.
# A tibble: 5 x 7
# Groups:   RestingECG [3]
  RestingECG Angina mean_age sd_age median_age max_age min_age
  <chr>      <chr>     <dbl>  <dbl>      <dbl>   <dbl>   <dbl>
1 LVH        N          55     9.54       54        65      46
2 LVH        Y          55.3   1.15       56        56      54
3 Normal     N          57.5  12.0        57.5      66      49
4 Normal     Y          57    NA          57        57      57
5 ST         Y          55    NA          55        55      55

NA는 RestingECG == "Normal"이면서 Angina == "Y"인 데이터가 1개 있어서 표준편차가 계산되지 않았습니다.