6  Data transform tidyverse

6.1 Introduction

tidyverse (https://www.tidyverse.org/)는 데이터 사이언스를 위한 R 기반의 독창적인 패키지들의 모음입니다. Rstudio의 핵심 전문가인 해들리위컴이 (Hadley Wickham) 중심이 되어 만들어 졌으며 기존의 툴보다 쉽고 효율적으로 데이터 분석을 수행할 수 있습니다.

데이터사이언스는 넓은 범위의 개념과 방법적인 정도가 있는 것은 아닙니다. 그러나 tidyverse의 목적은 데이터 분석을 위한 핵심이되는 고효율의 툴을 제공하는 것이며 그 철학은 다음과 같은 그림으로 요약할 수 있습니다.

from https://r4ds.had.co.nz/

6.2 Tibble object type

카이스트 강의

tibble 데이터 타입입 이해하기

R은 20년 이상된 비교적 오랜 역사를 가진 언어로서 data.frame 형태의 데이터 타입이 가장 많이 사용되고 있습니다. 그러나 당시에는 유용했던 기능이 시간이 흐르면서 몇몇 단점들이 드러나는 문제로 기존 코드를 그대로 유지한채 package 형태로 단점을 보완한 새로운 형태의 tibble 오브젝트 형식을 만들어 냈습니다. 대부분의 R 코드는 여전히 data.frame 형태의 데이터 타입을 사용하고 있으나 tidyverse에서는 tibble이 기본으로 사용되는 것을 참고하시기 바랍니다.

library(tidyverse)

tb <- tibble(
  x = 1:5, 
  y = 1, 
  z = x ^ 2 + y
)
tb

iris
as_tibble(iris)

tibbledata.frame과 다음 몇 가지 점이 다릅니다. data.frame의 경우 타입을 변환할 때 강제로 값의 타입을 바꾸거나 내부 변수의 이름을 바꾸는 경우가 있었으나 tibble은 이를 허용하지 않습니다. 샘플들 (row) 이름을 바꿀수도 없습니다. 또한 프린팅할 때 출력물에 나오는 정보가 다르며 마지막으로 data.frame은 subset에 대한 타입이 바뀔 경우가 있었지만 tibble은 바뀌지 않습니다.

x <- 1:3
y <- list(1:5, 1:10, 1:20)

data.frame(x, y)
tibble(x, y)

tibble은 컬럼 하나가 벡터형 변수가 아닌 리스트형 변수가 될 수 있다는 것도 data.frame과 다른 점 입니다.

names(data.frame(`crazy name` = 1))
names(tibble(`crazy name` = 1))

또한 다음과 같이 사용되는 변수의 (x) 참조 범위가 다릅니다.

data.frame(x = 1:5, y = x ^ 2)
tibble(x = 1:5, y = x ^ 2)
df1 <- data.frame(x = 1:3, y = 3:1)
class(df1)
class(df1[, 1:2])
class(df1[, 1])

df2 <- tibble(x = 1:3, y = 3:1)
class(df2)
class(df2[, 1:2])
class(df2[, 1])
class(df2$x)

6.3 Tidy data structure

카이스트 강의

tidy 데이터 이해하기. Long형과 wide형 데이터 구분은 분석 목적에 따라서 달라질 수 있으나 가능하면 tidy 형태의 데이터로 분석하는 것이 중요함.

데이터의 변수와 값을 구분하는 일은 적절한 데이터 분석을 위해 필수적인 과정입니다. 특히 복잡하고 사이즈가 큰 데이터일 경우는 더욱 중요할 수 있으나 경험에 의존해서 구분을 하는 것이 대부분 입니다. Tidy data는 이러한 변수와 값의 명확한 구분과 활용을 위한 데이터 구조중 하나 입니다 (Hadley Wickham. Tidy data. The Journal of Statistical Software, vol. 59, 2014).

tidy data는 다음과 같은 특징이 있습니다.

  • 각 변수는 해당하는 유일한 하나의 column을 가짐
  • 각 샘플은 해당하는 유일한 하나의 row를 가짐
  • 각 관측값은 해당하는 유일한 하나의 cell을 가짐

from https://r4ds.had.co.nz/

이러한 데이터 구조를 유지하는 것이 필요한 이유는 우선적으로 데이터 분석의 효율성에 있습니다. 또한 데이터를 일관성 있게 저장하고 관리하게 되면 그것을 다루는 분석 도구들을 배우고 활용하기 쉬워집니다. 특히 dplyr, ggplot2 및 tidyverse의 패키지들은 tidy 데이터 형태로 작동합니다. 그리고 변수들이 열에 배치되면서 백터연산을 지원하는 대부분의 R 함수들의 성능이 최대화 됩니다.

library(tidyverse)
library(readxl)
dat <- read_excel("examples/plate_reader.xls", sheet=1, skip = 0, col_names=T)

head(dat)
glimpse(dat)

위 데이터는 전형적인 long형 데이터 입니다. 각 변수는 하나의 컬럼에만 나타나고 각 샘플은 유일한 하나의 row를 가집니다. 만약 플레이터 한 장을 더 측정하면 아래쪽으로 동일한 컬럼에 추가 데이터가 붙게 됩니다. 그러나 임의의 데이터가 long 형인지 wide 형인지 판단하는 기준은 목적에 따라서 다를 수 있습니다. 이럴 경우 특정 목저을 가지고 2차원 평면에 plot을 그릴 때 어떤 데이터를 가지고 그림을 그릴지를 고려한다면 쉽게 판단이 가능합니다.

Tidy 데이터는 Long형 데이터로 알려져 있기도 합니다. 참고로 Wide형 데이터의 경우 샘플 데이터가 늘어날수록 row에 쌓이고 새로운 변수는 column에 쌓이는 방식으로 데이터가 확장되는 형태 입니다. 엑셀에서 볼 수 있는 일반적인 형식으로 다음 그림과 같습니다.

Long형 데이터의 경우 ID, variable, value 세가지 변수만 기억하면 되겠습니다. 위 wide형 데이터 경우를 보면 ID, variable, 그리고 value 이 세가지 요인이 주요 구성 요소임을 알 수 있습니다. Long형으로 변환할 경우 샘플을 참조할 수 있는 어떤 변수 (variable)도 ID가 될 수 있으며 2개 이상의 변수가 ID로 지정될 수 있습니다. 참고로 ID를 지정할 경우 해당 ID는 가능하면 중복되지 않는 값들을 갖는 변수를 사용해야 식별자로서 기능을 적절히 수행할 수 있습니다. Long형을 사용할 경우 데이터의 변수가 늘어나도 행의 수만 늘어나므로 코딩의 일관성과 변수들의 그룹을 만들어서 분석하는 등의 장점이 있습니다. 아래는 새로운 변수 F가 추가될 때 long 형 데이터에 데이터가 추가되는 경우를 나타낸 그림 입니다.

6.4 Pipe operator

카이스트 강의

파이프 오퍼레이터 사용법은 필수로 이해하며 기존 magrittr 오퍼레이터보다 네이티브 오퍼레이터 |> 추천

tidyverse 패키지를 활용하기 위해서는 파이프 오퍼레이터의 이해가 필요합니다. 기존 magrittr에서 제공하던 %>% 파이프 오퍼레이터와 함께 최근 R 4.1.0부터 도입된 네이티브 파이프 오퍼레이터(|>)를 사용할 수 있습니다. 작동법은 간단히 파이프 오퍼레이터의 왼쪽 코드의 결과를 출력으로 받아 오른쪽 코드의 입력 (첫번째 파라미터의 값)으로 받아들이는 작동을 합니다 (단축키: Shift+Ctrl+m). Tools > Global options > Code 에서 아래와 같이 Use native pipe operator 에 체크해주면 위 단축키를 누를 때 native pipe operator가 표시됩니다.

다음 예에서 보면 sin(pi) 와 같은 함수의 일반적인 사용법 대신 pi |> sin() 처럼 사용해도 똑같은 결과를 보여줍니다. cos(sin(pi))와 같이 여러 합수를 중첩하여 사용할 경우와 비교해서 코드의 가독성이나 효율 측면에서 크게 향상된 방법을 제공해 줍니다.

library(dplyr)

pi |> sin()
sin(pi)
pi |> sin() |> cos()
cos(sin(pi))

파이프 오퍼레이터는 특히 다음 설명할 dplyrgroup_by, split, filter, summary 등 행렬 편집/연산 함수를 빈번히 다양한 조합으로 쓰게되는 상황에서 더 큰 효과를 발휘할 수 있습니다. 일반적으로 파이프라인의 첫 단계 이후, 각 줄을 두 칸 들여쓰기합니다. 각 인자가 별도의 줄에 있으면 추가로 두 칸 더 들여쓰기를 합니다.

|>는 플레이스홀더로 _를 사용하고 magrittr의 %>% 플레이스홀더로 .을 사용합니다. magrittr의 %>%는 R 데이터 분석에서 오랜 시간 동안 널리 사용되어 온 파이프 오퍼레이터로, 고급 기능과 유연성을 제공하지만 최근에는 네이티브 파이프 오퍼레이터 (|>)의 사용이 권장되고 있으며 두 오퍼레이터 모두 R에서 데이터 처리와 분석을 보다 효율적이고 직관적으로 만들어주는 중요한 도구입니다.

다음 코드는 xpaste의 첫 번째 파라미터로 들어가게 되어 "1a", "2a", "3a", "4a", "5a"로 a 앞에 x 값들이 붙어서 출력된 것을 알 수 있습니다.

x <- 1:5
x |> paste("a", sep="")

특정 데이터셋의 컬럼별 평균을 구하고 각 평균의 합을 구할 경우를 생각해 봅시다. R에서는 colMeans라는 특별한 함수를 제공하여 컬럼별로 평균을 계산해 줍니다. 그 후 sum 함수를 사용하여 최종 원하는 값을 얻을 수 있습니다. 이러한 코드를 |> 오퍼레이터를 사용한 경우의 코드와 비교해 볼 수 있습니다.

x <- data.frame(x=c(1:100), y=c(201:300))
sum(colMeans(x))

x <- data.frame(x=c(1:100), y=c(201:300))
x |> 
  colMeans() |> 
  sum()

만약 두 번째 파라미터에 입력으로 왼쪽 구문의 출력을 받아들이고 싶을 경우는 플레이스 홀더_을 사용하면 되겠습니다. round 함수는 두 개의 파라미터를 설정할 있 이으며 digits 라는 두 번째 파라미터에 값을 pipe operator로 넘겨주고 싶을 경우 아래와 같이 표현할 수 있습니다.

6 |> round(pi, digits=_)
round(pi, digits=6)

6.5 Pivoting

카이스트 강의

Long형과 wide형 데이터를 서로 변환할 수 있는 방법 익히기

일반적으로 얻어지는 데이터의 형태는 wide형이며 이를 Long형으로 변환하기 위해서는 tidyverse 패키지에 속한 tidyr 패키지의 pivot_longerpivot_wider를 사용합니다. 기존에는 reshape2 패키지의 melt함수와 그 반대의 경우 dcast 함수를 사용할 수 있으나 tidyr 패키지가 널리 사용됩니다. wide형 데이터를 long형으로 변환하거나 long형을 wide형으로 변환하는 작업을 pivoting 이라고 합니다.

airquality 데이터는 wide형 데이터로도 볼 수 있고 long형 데이터로 볼 수도 있습니다. 앞에서 언급한 바와 같이 목적에 따라서 airquality 데이터가 wide형이 될 수 있고 long형도 될 수 있습니다. airquality 데이터는 특정 날짜에 airquality 지표 몇 가지에 대한 측정 값을 모아둔 데이터 입니다. 만약 Ozone과 Solar.R 만이 분석에 필요한 변수들이라면 두 개의 변수를 갖는 long형 데이터로 볼 수 도 있습니다.

카이스트 강의

아래 내용은 tidy 구조의 데이터를 사용할 경우 변수 이름으로 용이하게 분석할 수 있는 예시를 보여줌

airquality
myair2 <- airquality |> 
  dplyr::select(Ozone, Solar.R, Month, Day)

myair2 |> 
  pivot_longer()

myair2
ggplot(myair2, aes(x=Solar.R, y=Ozone)) +
  geom_point()

fit <- lm(myair2$Ozone~myair2$Solar.R)
summary(fit)

그러나 만약 각 컬럼을 airquality를 나타낼 수 있는 범주형 데이터 값으로 본다면 airquality 데이터는 wide형 데이터가 됩니다. 이 데이터를 long형으로 바꿀 경우 ID를 날짜로 하면 데이터들을 식별 할 수 있습니다. 그런데 날짜는 변수가 Month와 Day두 개로 나누어져 있으므로 다음과 같이 두 변수를 식별 변수로 (ID로) 사용 합니다. 확인을 위해 상위 5개의 데이터만 가지고 형 변환을 진행해 보겠습니다.

airquality

myair <- airquality[1:5,]
myair

myair_long <- pivot_longer(myair, c("Ozone", "Solar.R", "Wind", "Temp"))
myair_long 

myair_long <- myair |> 
  pivot_longer(c("Ozone", "Solar.R", "Wind", "Temp"))
myair_long 

myair_long2 <- myair |> 
  pivot_longer(c(Ozone, Solar.R, Wind, Temp))
myair_long2 

myair_long3 <- myair |> 
  pivot_longer(!c(Month, Day))
myair_long3

myair_long <- pivot_longer(airquality, c("Ozone", "Solar.R", "Wind", "Temp"))
myair_long |> 
  mutate(id = paste(Month, Day, sep="-")) |> 
  ggplot(aes(x=id, y=value, color=name)) +
  geom_point()

생성되는 long형 데이터의 변수 이름인 name과 value는 다음 파라메터를 지정하여 바꿀 수 있습니다.

myair_long <- myair |> 
  pivot_longer(c(Ozone, Solar.R, Wind, Temp), 
               names_to = "Type", 
               values_to = "Observation")
myair_long 

long형 데이터를 wide형 데이터로 변환 할 수도 있습니다.

myair_long |> 
  pivot_wider(
    names_from = Type, 
    values_from = Observation)

ggplot을 이용한 그래프 작성에는 위와 같은 long형 데이터가 주로 사용됩니다. R을 이용한 데이터 가시화는 dplyr 패키지로 wide형 데이터를 편집하고 pivot_longer 함수로 long형 데이터로 변환 후 ggplot을 이용하는 방식으로 수행합니다. 두 데이터 포멧에 대한 좀 더 구체적인 내용은 다음 링크를 참고하시기 바랍니다. https://www.theanalysisfactor.com/wide-and-long-data/

카이스트 강의

대부분 R 함수들이 벡터연산을 지원하고 있으며 컬럼을 변수로 하는 long형 데이터(컬럼이 변수)로 정형화해서 사용할 경우 R의 성능을 극대화 할 수 있음

library(ggplot2)
data(msleep)
head(msleep)

ggplot(msleep, aes(x = brainwt, y = sleep_total)) +
  geom_point() +
  scale_x_log10() +
  xlab("Brain Weight (log scale)") +
  ylab("Total Sleep Time (hours)") +
  ggtitle("Relationship between Brain Weight and Total Sleep Time")
카이스트 강의
  • plate read 예제, 샘플: B, C, D, E, F, G 6종, 처리농도: 1~6, 반복: 7~12
  • sample name 변수 추가
  • 농도 변수 추가
  • 반복 나타내는 dummy 변수 추가
  • plot

library(tidyverse)
library(readxl)
dat <- read_excel("examples/plate_reader.xls", sheet=1, skip = 0, col_names=T)
dat

newdat <- dat |> 
  mutate(sample = stringr::str_sub(Well, 1, 1)) |> 
  mutate(cpos = as.numeric(stringr::str_sub(Well, 2, -1))) |> 
  mutate(conc = case_when(cpos < 7 ~ cpos,
                          TRUE ~ cpos-6)
  ) |> 
  mutate(dupl = case_when(cpos < 7 ~ 1,
                          TRUE ~ 2)) |> 
  rename(od = "595nm_kk (A)",
         gfp = "EGFP_sulim (Counts)") |> 
  dplyr::select(sample, conc, dupl, od, gfp)


newdat |> 
  pivot_longer(c("od", "gfp")) |> 
  mutate(id = paste(sample, conc, dupl, sep="-")) |> 
  ggplot(aes(x=id, y=value, color=name)) +
  geom_point() 
  • 농도에 따른 OD, GFP 변화 plot 그리기
  • 처리농도: 0, 0.1, 0.5, 1, 5, 10
  • 문자형 값은 factor형으로 변환 필요
conc_val <- c(0, 0.1, 0.5, 1, 5, 10)
conc_val[1]

newdat |> 
  mutate(conc_new = conc_val[conc])

newdat2 <- newdat |> 
  mutate(conc = conc_val[conc]) |> 
  mutate(dupl = as.character(dupl)) |> 
  mutate_if(is.character, as.factor) 

newdat2 |> 
  ggplot(aes(x=conc, y=od)) +
  geom_point()

newdat2 |> 
  ggplot(aes(x=conc, y=od, color=sample)) +
  geom_point() 
  • growth data가 아님
  • bargraph, x축 샘플/농도, y축 od
newdat2 |> 
  ggplot(aes(x=sample, y=od)) +
  geom_bar()

newdat2 |> 
  ggplot(aes(x=sample, y=od)) +
  geom_bar(stat = "identity")

newdat2 |> 
  ggplot(aes(x=sample, y=od, color=conc)) +
  geom_bar(stat = "identity")

newdat2 |> 
  ggplot(aes(x=sample, y=od, fill=conc)) +
  geom_bar(stat = "identity")

newdat2 |> 
  ggplot(aes(x=sample, y=od, fill=conc)) +
  geom_bar(stat = "identity")


newdat2 |> 
  mutate(conc = as.factor(conc)) |> 
  ggplot(aes(x=sample, y=od, fill=conc)) +
  geom_bar(stat = "identity")

newdat2 |> 
  mutate(conc = as.factor(conc)) |> 
  ggplot(aes(x=sample, y=od, fill=conc)) +
  geom_bar(stat = "identity", position = "dodge")
  • 다른색으로
library(RColorBrewer)
display.brewer.all()

newdat2 |> 
  mutate(conc = as.factor(conc)) |> 
  ggplot(aes(x=sample, y=od, fill=conc)) +
  geom_bar(stat = "identity", position = "dodge") +
  scale_fill_brewer(palette = "YlGnBu")

6.6 Separating and uniting

데이터를 분석할 때 하나의 컬럼에 두 개 이상의 변수값이 저장되어 있거나 두 개의 변수를 하나의 컬럼으로 합해야 하는 경우가 종종 있습니다. 전자의 경우 separate() 함수를 사용해서 두 변수(컬럼)으로 나누어 줄 수 있으며 후자의 경우 unite() 함수를 사용하여 두 변수를 하나의 값으로 병합할 수 있습니다. 다음은 airquality데이터에서 Month와 Day 변수를 하나의 컬럼으로 병합하여 Date라는 변수로 만들어 주는 경우의 예 입니다.

newairquality <- airquality |> 
  unite(Date, Month, Day, sep=".")
newairquality

separate()함수를 사용하면 다음과 같이 해당 변수의 값을 나누어 다시 두 개의 변수(컬럼)으로 나누어 줄 수 있습니다.

newairquality |> 
  separate(col=Date, into = c("Month", "Day"), sep = "\\.")

6.7 dplyr

카이스트 강의

데이터를 다루기 위한 tidyverse의 가장 핵심이 되는 패키지. 여기서 사용되는 기술은 Genome 데이터 등 여러 타입의 데이터에 대해서 응용해서 활용되고 있음.

dplyr (https://dplyr.tidyverse.org/) 은 ggplot2을 개발한 해들리위컴이 (Hadley Wickham) 중심이 되어 만들어 졌으며 ggplot2와 함께 tidyverse의 (https://www.tidyverse.org/) 핵심 패키지 입니다. dplyr은 데이터를 다루는 크기나 분석의 속도, 편의성을 향상시켜 새롭게 만들어놓은 패키지 입니다. 기존 apply와 같은 행렬 연산 기능과 subset, split, group 와 같은 행렬 편집 기능을 더하여 만들어진 도구라고 할 수 있습니다.

dplyr의 전신이라 할 수 있는 plyr 패키지는 다음과 같이 설명이 되어 있습니다. A set of tools for a common set of problems: you need to split up a big data structure into homogeneous pieces, apply a function to each piece and then combine all the results back together. 즉 split-apply-combine 세 가지 동작을 쉽게 할 수 있도록 만들어 놓은 툴 입니다. R이 다른 언어에 비해 데이터 분석에서 주목을 받는 이유로 split, apply 등의 행렬 연산 함수가 발달한 것을 내세우는데 dplyr은 이들을 보다 더 편리하게 사용할 수 있도록 만들어 놓은 것 입니다.

이제 dplyr 패키지에서 제공하는 함수를 사용해 보겠습니다. dplyr을 구성하는 중요한 함수는 다음과 같습니다.

  • select() - 변수 (columns) 선택

  • filter() - 샘플 (rows) 선택

  • arrange() - 샘플들의 정렬 순서 변경

  • mutate() - 새로운 변수 만들기

  • summarise() - 대표값 만들기

  • group_by() - 그룹별로 계산 수행

  • join() - 두 tibble 또는 data.frame을 병합할 때 사용

  • 위 함수들과 (특히 filter, select, mutate, summarise) 조합하여 (함수 내에서) 사용할 수 있는 helper 함수들이 같이 사용될 수 있습니다 (독립적으로도 사용 가능).

    • across
    • if_any
    • if_all
    • everything
    • starts_with
    • end_with
    • contains

이 함수들은 %>%와 함께 쓰이면서 강력한 성능을 발휘합니다. summarise 함수는 특정 값들의 통계 값을 계산해 주는 함수이며 그 외 함수들은 행렬 편집을 위한 함수들로 보시면 되겠습니다. 간단한 예제를 수행하면서 각각의 기능을 살펴보고 왜 dplyr이 널리 사용되고 그 장점이 무엇인지 파악해 보도록 하겠습니다.

6.7.1 select

select() 는 주어진 데이터셋으로부터 관심있는 변수를 (column) 선택하여 보여줍니다.

head(iris)
iris |> 
  select(Species, everything()) |> 
  head(5)
iris |> select(Species, everything())
iris |> select(-Species)

다음 helper 함수들은 select 함수와 같이 유용하게 쓰일 수 있습니다.

starts_with(“abc”) - “abc” 로 시작하는 문자열을 갖는 변수 이름 ends_with(“xyz”) - “xyz”으로 끝나는 문자열을 갖는 변수 이름 contains(“ijk”) - “ijk” 문자열을 포함하는 변수 이름 matches(“(.)\1”) - 정규식, 반복되는 문자

iris |> select(starts_with('S'))
iris |> select(obs = starts_with('S'))

아래는 matches 함수를 사용한 방법 입니다. 좀 더 복잡한 패턴을 적용하여 변수들을 선택할 수 있으며 grep 함수를 사용할 경우도 정규식 패턴을 적용할 수 있습니다.

iris2 <- rename(iris, aavar = Petal.Length)
select(iris2, matches("(.)\\1"))
tmp <-iris[,3:5]
colnames(iris)[grep("^S", colnames(iris))]
iris[,grep("^S", colnames(iris))]
tmp

아래 (.)\\1은 하나의 문자 .가 (어떤 문자든) 한 번 더 \\1 사용된 변수 이름을 말하며 이는 aavaraa밖에 없으므로 aavar가 선택됩니다. grep에서 ^ 표시는 맨 처음을 나타내므로 ^S는 S로 시작하는 문자가 되겠습니다. 따라서 grep("^S", colnames(iris))의 경우 컬럼 이름 중 S로 시작하는 이름은 True로 그렇지 않으면 False 값을 리턴합니다.

6.7.2 filter

filter 함수를 사용해서 원하는 조건의 데이터 (샘플)을 골라낼 수 있습니다.

library(dplyr)

head(iris)
iris |> 
  filter(Species=="setosa")

iris |> 
  filter(Species=="setosa" | Species=="versicolor")

iris |> 
  filter(Species=="setosa" & Species=="versicolor")

iris |> 
  filter(Species=="setosa" | Species=="versicolor") |> 
  dim()

filter,로 구분되는 매개변수는 and 로직으로 묶인 조건입니다. 지난 강좌에서 보셨듯 R에서 and&, or|, 그리고 not은 ! 으로 사용하면 되며 filter에서 ,로 구분된 조건은 and와 같다고 보시면 되겠습니다.

Image from (https://r4ds.had.co.nz/)

6.7.3 arrange

arrange()는 지정된 변수를 기준으로 값의 크기순서로 샘플들의 배열 순서 즉, row의 순서를 바꾸는 기능을 수행합니다. 기본으로 크기가 커지는 순서로 정렬이 진행되며 작아지는 순서를 원할 경우 desc 함수를 사용할 수 있습니다.

iris |> arrange(Sepal.Length)
iris |> arrange(desc(Sepal.Length))
iris |> arrange(Sepal.Length, Sepal.Width)

6.7.4 mutate

mutate() 함수는 새로운 변수를 추가할 수 있는 기능을 제공하며 앞에서 배웠던 within()과 비슷하다고 볼 수 있습니다. 아래와 같이 mutate함수는 sepal_ratio라는 변수를 새로 만들어서 기존 iris 데이터들과 함께 반환해 줍니다.

iris2 <- iris |> mutate(sepal_ratio = Sepal.Length/Sepal.Width)
head(iris2)

6.7.5 summarise

summarise()data.frame내 특정 변수의 값들로 하나의 요약값/대푯값을 만들어 줍니다. summarise 함수는 단독으로 쓰이기 보다는 group_by() 기능과 병행해서 쓰이는 경우에 유용하게 쓰입니다. summarise_all() 함수를 사용하면 모든 변수에 대해서 지정된 함수를 실행합니다. 특히 summarise 함수는 다음과 같이 across, if_any, if_all 등의 helper 함수와 조합되어 사용이 가능합니다.

iris |> summarise(mean(Sepal.Length), m=mean(Sepal.Width))
iris |> 
  group_by(Species) |> 
  summarise(mean(Sepal.Width))

iris |> 
  group_by(Species) |> 
  summarise_all(mean)

iris |> 
  group_by(Species) |> 
  summarise(across(everything(), mean))


iris |> 
  group_by(Species) |> 
  summarise_all(sd)

iris |> 
  group_by(Species) |> 
  summarise(across(everything(), sd))
카이스트 강의
  • 96 플레이트 데이터를 읽고 각 반복에 대한 평균과 표준편차 계산
  • 샘플별로 농도별로 막대그래프 그리고 표준편차 표시
library(tidyverse)
library(readxl)
dat <- read_excel("examples/plate_reader.xls", sheet=1, skip = 0, col_names=T)
conc_val <- c(0, 0.1, 0.5, 1, 5, 10)


newdat <- dat |> 
  mutate(sample = stringr::str_sub(Well, 1, 1)) |> 
  mutate(cpos = as.numeric(stringr::str_sub(Well, 2, -1))) |> 
  mutate(conc = case_when(cpos < 7 ~ cpos,
                          TRUE ~ cpos-6)) |> 
  mutate(dupl = case_when(cpos < 7 ~ 1,
                          TRUE ~ 2)) |> 
  rename(od = "595nm_kk (A)",
         gfp = "EGFP_sulim (Counts)") |> 
  dplyr::select(sample, conc, dupl, od, gfp)

newdat |> 
  mutate(conc_new = conc_val[conc])

newdat2 <- newdat |> 
  mutate(conc = as.factor(conc_val[conc])) |> 
  mutate(dupl = as.character(dupl)) |> 
  mutate_if(is.character, as.factor) |> 
  group_by(sample, conc) |> 
  summarise(m = mean(gfp), s = sd(gfp))


newdat2 |> 
  ggplot(aes(x=sample, y=m, fill = conc)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_errorbar(mapping = aes(ymin = m-s, ymax = m+s), position = position_dodge2(width = 0.5, padding = 0.5))

6.7.6 join

join 함수는 데이터를 병합해주는 기능을 수행하는 함수 입니다. 네 가지 종류의 함수가 있으며 (left_join(), ’right_join(), 'inner_join(), ’full_join()) 기본적으로 공통되는 이름의 변수를 (key) 이용해서 공통되는 샘플끼리 자동으로 병합해 주는 기능을 수행합니다.by`에서 지정해준 파라메터의 값을 기준으로 기능이 수행 됩니다.

df1 <- data.frame(id=c(1,2,3,4,5,6), age=c(30, 41, 33, 56, 20, 17))
df2 <- data.frame(id=c(4,5,6,7,8,9), gender=c("f", "f", "m", "m", "f", "m"))

inner_join(df1, df2, by="id")
left_join(df1, df2, "id")
right_join(df1, df2, "id")
full_join(df1, df2, "id")

# vs.
cbind(df1, df2)

6.8 Code comparison

이제 split, apply, combine을 활용하여 평균을 구하는 코드와 dplyr 패키지를 사용하여 만든 코드를 비교해 보도록 하겠습니다. iris 데이터를 분석하여 품종별로 꽃받침의 길이 (Sepal.length)의 평균과 표준편차, 그리고 샘플의 수를 구해보는 코드입니다.

splitfactor형 변수인 Species를 기준으로 iris 데이터를 나누어 주는 역할을 하며 lapplylist 형 데이터인 iris_split을 각 리스트의 각각의 원소들에 대해서 임의의 함수 function(x)... 를 수행하는 역할을 합니다. 마지막 data.frame으로 최종 경로를 combine 합니다.

iris_split <- split(iris, iris$Species)
iris_means <- lapply(iris_split, function(x){mean(x$Sepal.Length)})
iris_sd <- lapply(iris_split, function(x){sd(x$Sepal.Length)})
iris_cnt <- lapply(iris_split, function(x){length(x$Sepal.Length)})
iris_df <- data.frame(unlist(iris_cnt), unlist(iris_means), unlist(iris_sd))

아래는 dplyr 패키지를 사용한 코드 입니다.

iris_df <- iris |> 
  group_by(Species) |> 
  summarise(n=n(), mean=mean(Sepal.Length), sd=sd(Sepal.Length))

위에서 보듯 dplyr 패키지를 사용할 경우 그 결과는 같으나 코드의 가독성과 효율성면에서 장점을 보여줍니다. iris 데이터를 받아서 Species에 명시된 그룹으로 나누고 원하는 함수를 타깃 컬럼에 대해서 적용하라는 의미 입니다. 다음은 모든 변수에 대한 평균을 구하는 코드 입니다.

iris_mean_df <- iris |> 
  group_by(Species) |> 
  summarise(across(everything(), mean))

자세한 ggplot의 내용은 다음시간에 학습하겠지만 각 평균에 대한 막대그래프를 그러보겠습니다.

library(ggplot2)

iris_mean_df2 <- iris_mean_df |> 
  pivot_longer(-Species)

ggplot(iris_mean_df2, aes(x=Species, y=value, fill=name)) +
  geom_bar(stat="identity", position="dodge")


This work is available under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.