5 데이터 정리
5.1 소개
“행복한 가정은 모두 비슷하고, 불행한 가정은 모두 저마다의 이유로 불행하다.” — 레프 톨스토이
“깔끔한(tidy) 데이터셋은 모두 비슷하지만, 지저분한(messy) 데이터셋은 모두 저마다의 방식으로 지저분하다.” — 해들리 위컴
이 장에서는 깔끔한 데이터(tidy data) 라는 시스템을 사용하여 R에서 데이터를 일관되게 구성하는 방법을 배울 것입니다. 데이터를 이 형식으로 만드는 데는 초기에 약간의 작업이 필요하지만, 그 작업은 장기적으로 성과를 냅니다. 일단 깔끔한 데이터와 tidyverse의 패키지에서 제공하는 깔끔한 도구를 갖추면, 한 표현에서 다른 표현으로 데이터를 뭉개는 데(munging) 훨씬 적은 시간을 소비하게 되어, 관심 있는 데이터 질문에 더 많은 시간을 할애할 수 있습니다.
이 장에서는 먼저 깔끔한 데이터의 정의를 배우고 간단한 장난감 데이터셋에 적용되는 것을 볼 것입니다. 그런 다음 데이터 정리에 사용할 주요 도구인 피벗(pivoting)에 대해 자세히 알아볼 것입니다. 피벗을 사용하면 값을 변경하지 않고도 데이터의 형태를 변경할 수 있습니다.
5.1.1 선수 지식
이 장에서는 지저분한 데이터셋을 정리하는 데 도움이 되는 많은 도구를 제공하는 패키지인 tidyr에 초점을 맞출 것입니다. tidyr은 핵심 tidyverse의 멤버입니다.
이 장부터는 library(tidyverse)의 로드 메시지를 숨깁니다.
5.2 깔끔한 데이터(Tidy data)
동일한 기본 데이터를 여러 가지 방식으로 표현할 수 있습니다. 아래 예제는 동일한 데이터를 세 가지 다른 방식으로 구성한 것을 보여줍니다. 각 데이터셋은 네 가지 변수인 국가(country), 연도(year), 인구(population), 결핵(TB) 사례(cases) 수에 대해 동일한 값을 보여주지만, 각 데이터셋은 값을 다른 방식으로 구성합니다.
table1
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1999 745 19987071
#> 2 Afghanistan 2000 2666 20595360
#> 3 Brazil 1999 37737 172006362
#> 4 Brazil 2000 80488 174504898
#> 5 China 1999 212258 1272915272
#> 6 China 2000 213766 1280428583
table2
#> # A tibble: 12 × 4
#> country year type count
#> <chr> <dbl> <chr> <dbl>
#> 1 Afghanistan 1999 cases 745
#> 2 Afghanistan 1999 population 19987071
#> 3 Afghanistan 2000 cases 2666
#> 4 Afghanistan 2000 population 20595360
#> 5 Brazil 1999 cases 37737
#> 6 Brazil 1999 population 172006362
#> # ℹ 6 more rows
table3
#> # A tibble: 6 × 3
#> country year rate
#> <chr> <dbl> <chr>
#> 1 Afghanistan 1999 745/19987071
#> 2 Afghanistan 2000 2666/20595360
#> 3 Brazil 1999 37737/172006362
#> 4 Brazil 2000 80488/174504898
#> 5 China 1999 212258/1272915272
#> 6 China 2000 213766/1280428583이것들은 모두 동일한 기본 데이터의 표현이지만 사용하기에 똑같이 쉽지는 않습니다. 그중 하나인 table1은 깔끔하기(tidy) 때문에 tidyverse 내에서 작업하기가 훨씬 쉬울 것입니다.
데이터셋을 깔끔하게 만드는 세 가지 상호 관련된 규칙이 있습니다:
- 각 변수는 하나의 열(column)이어야 하고, 각 열은 하나의 변수여야 합니다.
- 각 관측값은 하나의 행(row)이어야 하고, 각 행은 하나의 관측값이어야 합니다.
- 각 값은 하나의 셀(cell)이어야 하고, 각 셀은 하나의 값이어야 합니다.
Figure 5.1 는 규칙을 시각적으로 보여줍니다.
왜 데이터가 깔끔한지 확인해야 할까요? 두 가지 주요 이점이 있습니다:
데이터를 저장하는 일관된 방식을 선택하는 것에는 일반적인 이점이 있습니다. 일관된 데이터 구조가 있으면 근본적인 통일성이 있기 때문에 그와 함께 작동하는 도구를 배우기가 더 쉽습니다.
변수를 열에 배치하는 것에는 구체적인 이점이 있는데, 이를 통해 R의 벡터화된 특성이 빛을 발할 수 있기 때문입니다. Section 3.3.1 와 Section 3.5.2 에서 배웠듯이 대부분의 내장 R 함수는 값의 벡터와 함께 작동합니다. 따라서 깔끔한 데이터를 변형하는 것이 특히 자연스럽게 느껴집니다.
dplyr, ggplot2 및 tidyverse의 다른 모든 패키지는 깔끔한 데이터와 함께 작동하도록 설계되었습니다. 다음은 table1로 작업하는 방법을 보여주는 몇 가지 작은 예입니다.
# 10,000명당 비율 계산
table1 |>
mutate(rate = cases / population * 10000)
#> # A tibble: 6 × 5
#> country year cases population rate
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1999 745 19987071 0.373
#> 2 Afghanistan 2000 2666 20595360 1.29
#> 3 Brazil 1999 37737 172006362 2.19
#> 4 Brazil 2000 80488 174504898 4.61
#> 5 China 1999 212258 1272915272 1.67
#> 6 China 2000 213766 1280428583 1.67
# 연도별 총 사례 수 계산
table1 |>
group_by(year) |>
summarize(total_cases = sum(cases))
#> # A tibble: 2 × 2
#> year total_cases
#> <dbl> <dbl>
#> 1 1999 250740
#> 2 2000 296920
# 시간 경과에 따른 변화 시각화
ggplot(table1, aes(x = year, y = cases)) +
geom_line(aes(group = country), color = "grey50") +
geom_point(aes(color = country, shape = country)) +
scale_x_continuous(breaks = c(1999, 2000)) # x-axis breaks at 1999 and 20005.2.1 연습문제
각 샘플 테이블에 대해 각 관측값과 각 열이 무엇을 나타내는지 설명하세요.
-
table2와table3에 대한rate를 계산하는 데 사용할 프로세스를 스케치하세요. 네 가지 작업을 수행해야 합니다:- 국가별 연도별 결핵 사례 수를 추출합니다.
- 일치하는 국가별 연도별 인구를 추출합니다.
- 사례를 인구로 나누고 10000을 곱합니다.
- 적절한 위치에 다시 저장합니다.
이러한 작업을 실제로 수행하는 데 필요한 모든 함수를 아직 배우지는 않았지만 필요한 변형에 대해 생각할 수는 있어야 합니다.
5.3 데이터 길게 늘리기
깔끔한 데이터의 원칙이 너무 뻔해 보여서 깔끔하지 않은 데이터셋을 만날 일이 있을지 궁금할 수도 있습니다. 하지만 불행히도 대부분의 실제 데이터는 깔끔하지 않습니다. 두 가지 주요 이유가 있습니다:
데이터는 종종 분석 이외의 목표를 용이하게 하기 위해 구성됩니다. 예를 들어, 분석이 아닌 데이터 입력을 쉽게 하기 위해 데이터가 구조화되는 것이 일반적입니다.
대부분의 사람들은 깔끔한 데이터의 원칙에 익숙하지 않으며, 데이터를 다루는 데 많은 시간을 보내지 않는 한 스스로 도출하기 어렵습니다.
즉, 대부분의 실제 분석에는 최소한 약간의 정리가 필요합니다. 근본적인 변수와 관측값이 무엇인지 파악하는 것으로 시작할 것입니다. 때로는 쉽지만, 다른 경우에는 데이터를 처음 생성한 사람들과 상의해야 할 수도 있습니다. 다음으로, 변수는 열에 있고 관측값은 행에 있는 깔끔한 형태로 데이터를 피벗(pivot) 합니다.
tidyr은 데이터를 피벗하기 위한 두 가지 함수인 pivot_longer()와 pivot_wider()를 제공합니다. 가장 일반적인 경우이기 때문에 pivot_longer()부터 시작하겠습니다. 몇 가지 예제를 살펴보겠습니다.
5.3.1 열 이름에 있는 데이터
billboard 데이터셋은 2000년 노래의 빌보드 순위를 기록합니다:
billboard
#> # A tibble: 317 × 79
#> artist track date.entered wk1 wk2 wk3 wk4 wk5
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2 Pac Baby Don't Cry (Ke… 2000-02-26 87 82 72 77 87
#> 2 2Ge+her The Hardest Part O… 2000-09-02 91 87 92 NA NA
#> 3 3 Doors Down Kryptonite 2000-04-08 81 70 68 67 66
#> 4 3 Doors Down Loser 2000-10-21 76 76 72 69 67
#> 5 504 Boyz Wobble Wobble 2000-04-15 57 34 25 17 17
#> 6 98^0 Give Me Just One N… 2000-08-19 51 39 34 26 26
#> # ℹ 311 more rows
#> # ℹ 71 more variables: wk6 <dbl>, wk7 <dbl>, wk8 <dbl>, wk9 <dbl>, …이 데이터셋에서 각 관측값은 노래입니다. 처음 세 열(artist, track, date.entered)은 노래를 설명하는 변수입니다. 그 다음에는 매주 노래의 순위를 설명하는 76개의 열(wk1-wk76)이 있습니다1. 여기서 열 이름은 하나의 변수(week)이고 셀 값은 다른 변수(rank)입니다.
이 데이터를 정리하기 위해 pivot_longer()를 사용할 것입니다:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank"
)
#> # A tibble: 24,092 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> 7 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk7 99
#> 8 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk8 NA
#> 9 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk9 NA
#> 10 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk10 NA
#> # ℹ 24,082 more rows데이터 뒤에는 세 가지 주요 인수가 있습니다:
-
cols는 피벗해야 하는 열, 즉 변수가 아닌 열을 지정합니다. 이 인수는select()와 동일한 구문을 사용하므로 여기서는!c(artist, track, date.entered)또는starts_with("wk")를 사용할 수 있습니다. -
names_to는 열 이름에 저장된 변수의 이름을 지정하며, 우리는 그 변수의 이름을week라고 지었습니다. -
values_to는 셀 값에 저장된 변수의 이름을 지정하며, 우리는 그 변수의 이름을rank라고 지었습니다.
코드에서 "week"와 "rank"는 인용부호로 묶여 있는데, 이는 우리가 생성하는 새 변수이며 pivot_longer() 호출을 실행할 때 데이터에 아직 존재하지 않기 때문입니다.
이제 결과로 나온 더 긴 데이터 프레임에 주의를 돌려봅시다. 노래가 76주 미만 동안 상위 100위 안에 있으면 어떻게 될까요? 2 Pac의 “Baby Don’t Cry”를 예로 들어보겠습니다. 위의 출력은 이 노래가 7주 동안만 상위 100위 안에 있었고 나머지 모든 주는 결측값으로 채워져 있음을 시사합니다. 이러한 NA는 실제로 알 수 없는 관측값을 나타내는 것이 아니라 데이터셋의 구조에 의해 강제로 존재하게 된 것이므로2, values_drop_na = TRUE를 설정하여 pivot_longer()에 제거하도록 요청할 수 있습니다:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
)
#> # A tibble: 5,307 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> # ℹ 5,301 more rows이제 행 수가 훨씬 줄어들어 NA가 있는 많은 행이 삭제되었음을 나타냅니다.
또한 노래가 76주 이상 상위 100위 안에 있으면 어떻게 되는지 궁금할 수도 있습니다. 이 데이터에서는 알 수 없지만 추가 열 wk77, wk78, …이 데이터셋에 추가될 것이라고 추측할 수 있습니다.
이 데이터는 이제 깔끔하지만, mutate()와 readr::parse_number()를 사용하여 week의 값을 문자열에서 숫자로 변환하면 향후 계산을 조금 더 쉽게 만들 수 있습니다. parse_number()는 다른 모든 텍스트를 무시하고 문자열에서 첫 번째 숫자를 추출하는 편리한 함수입니다.
billboard_longer <- billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
) |>
mutate(
week = parse_number(week)
)
billboard_longer
#> # A tibble: 5,307 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <dbl> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 6 94
#> # ℹ 5,301 more rows이제 모든 주 번호가 하나의 변수에 있고 모든 순위 값이 다른 변수에 있으므로 시간 경과에 따른 노래 순위 변화를 시각화하기에 좋은 위치에 있습니다. 코드는 아래와 같고 결과는 Figure 5.2 에 있습니다. 20주 이상 상위 100위 안에 머무르는 노래가 거의 없음을 알 수 있습니다.
billboard_longer |>
ggplot(aes(x = week, y = rank, group = track)) +
geom_line(alpha = 0.25) +
scale_y_reverse()5.3.2 피벗은 어떻게 작동하나요?
이제 피벗을 사용하여 데이터의 모양을 바꾸는 방법을 보았으므로, 피벗이 데이터에 어떤 작업을 수행하는지에 대한 직관을 얻기 위해 시간을 조금 투자해 봅시다. 무슨 일이 일어나고 있는지 보기 쉽도록 아주 간단한 데이터셋으로 시작하겠습니다. id가 A, B, C인 세 명의 환자가 있고 각 환자에 대해 두 번의 혈압 측정을 한다고 가정해 봅시다. 작은 티블을 손으로 구성하는 편리한 함수인 tribble()로 데이터를 만들 것입니다:
df <- tribble(
~id, ~bp1, ~bp2,
"A", 100, 120,
"B", 140, 115,
"C", 120, 125
)우리는 새 데이터셋이 세 가지 변수인 id(이미 존재함), measurement(열 이름), value(셀 값)를 갖기를 원합니다. 이를 달성하려면 df를 더 길게 피벗해야 합니다:
df |>
pivot_longer(
cols = bp1:bp2,
names_to = "measurement",
values_to = "value"
)
#> # A tibble: 6 × 3
#> id measurement value
#> <chr> <chr> <dbl>
#> 1 A bp1 100
#> 2 A bp2 120
#> 3 B bp1 140
#> 4 B bp2 115
#> 5 C bp1 120
#> 6 C bp2 125형태 변경(reshaping)은 어떻게 작동할까요? 열별로 생각하면 더 쉽게 알 수 있습니다. Figure 5.3 에 표시된 것처럼 원본 데이터셋에서 이미 변수였던 열(id)의 값은 피벗되는 각 열에 대해 한 번씩 반복되어야 합니다.
열 이름은 Figure 5.4 에 표시된 것처럼 names_to에 의해 정의된 이름의 새 변수에서 값이 됩니다. 원본 데이터셋의 각 행에 대해 한 번씩 반복되어야 합니다.
셀 값 또한 values_to에 의해 정의된 이름을 가진 새 변수의 값이 됩니다. 행별로 풀립니다. Figure 5.5 는 프로세스를 보여줍니다.
5.3.3 열 이름에 많은 변수가 있는 경우
열 이름에 여러 정보가 섞여 있어 이를 별도의 새 변수에 저장하고 싶을 때 더 어려운 상황이 발생합니다. 예를 들어 위에서 본 table1과 친구들의 소스인 who2 데이터셋을 가져와 보겠습니다:
who2
#> # A tibble: 7,240 × 58
#> country year sp_m_014 sp_m_1524 sp_m_2534 sp_m_3544 sp_m_4554
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1980 NA NA NA NA NA
#> 2 Afghanistan 1981 NA NA NA NA NA
#> 3 Afghanistan 1982 NA NA NA NA NA
#> 4 Afghanistan 1983 NA NA NA NA NA
#> 5 Afghanistan 1984 NA NA NA NA NA
#> 6 Afghanistan 1985 NA NA NA NA NA
#> # ℹ 7,234 more rows
#> # ℹ 51 more variables: sp_m_5564 <dbl>, sp_m_65 <dbl>, sp_f_014 <dbl>, …세계보건기구(WHO)에서 수집한 이 데이터셋은 결핵 진단에 대한 정보를 기록합니다. 이미 변수이고 해석하기 쉬운 두 개의 열 country와 year가 있습니다. 그 뒤에는 sp_m_014, ep_m_4554, rel_m_3544와 같은 56개의 열이 있습니다. 이 열들을 충분히 오래 쳐다보면 패턴이 있음을 알 수 있습니다. 각 열 이름은 _로 구분된 세 부분으로 구성됩니다. 첫 번째 부분인 sp/rel/ep는 진단에 사용된 방법을 설명하고, 두 번째 부분인 m/f는 성별(gender, 이 데이터셋에서는 이진 변수로 코딩됨)이며, 세 번째 부분인 014/1524/2534/3544/4554/5564/65는 나이(age) 범위(예: 014는 0-14세를 나타냄)입니다.
따라서 이 경우 who2에는 6가지 정보가 기록되어 있습니다: 국가와 연도(이미 열임), 진단 방법, 성별 범주, 연령대 범주(다른 열 이름에 포함됨), 해당 범주의 환자 수(셀 값). 이 6가지 정보를 6개의 별도 열로 구성하기 위해 names_to에 열 이름 벡터를 사용하고, 원래 변수 이름을 조각으로 나누기 위한 지시자로 names_sep을 사용하며, values_to에 열 이름을 사용하여 pivot_longer()를 씁니다:
who2 |>
pivot_longer(
cols = !(country:year),
names_to = c("diagnosis", "gender", "age"),
names_sep = "_",
values_to = "count"
)
#> # A tibble: 405,440 × 6
#> country year diagnosis gender age count
#> <chr> <dbl> <chr> <chr> <chr> <dbl>
#> 1 Afghanistan 1980 sp m 014 NA
#> 2 Afghanistan 1980 sp m 1524 NA
#> 3 Afghanistan 1980 sp m 2534 NA
#> 4 Afghanistan 1980 sp m 3544 NA
#> 5 Afghanistan 1980 sp m 4554 NA
#> 6 Afghanistan 1980 sp m 5564 NA
#> # ℹ 405,434 more rowsnames_sep의 대안은 names_pattern으로, Chapter 15 에서 정규 표현식에 대해 배우고 나면 더 복잡한 명명 시나리오에서 변수를 추출하는 데 사용할 수 있습니다.
개념적으로 이것은 이미 본 더 간단한 경우의 작은 변형일 뿐입니다. Figure 5.6 는 기본 아이디어를 보여줍니다. 이제 열 이름이 단일 열로 피벗되는 대신 여러 열로 피벗됩니다. 두 단계(먼저 피벗한 다음 분리)로 일어난다고 상상할 수 있지만 내부적으로는 더 빠르기 때문에 단일 단계로 일어납니다.
5.3.4 열 헤더의 데이터 및 변수 이름
복잡성의 다음 단계는 열 이름에 변수 값과 변수 이름이 섞여 있는 경우입니다. 예를 들어 household 데이터셋을 가져와 보겠습니다:
household
#> # A tibble: 5 × 5
#> family dob_child1 dob_child2 name_child1 name_child2
#> <int> <date> <date> <chr> <chr>
#> 1 1 1998-11-26 2000-01-29 Susan Jose
#> 2 2 1996-06-22 NA Mark <NA>
#> 3 3 2002-07-11 2004-04-05 Sam Seth
#> 4 4 2004-10-10 2009-08-27 Craig Khai
#> 5 5 2000-12-05 2005-02-28 Parker Gracie이 데이터셋에는 최대 두 자녀의 이름과 생년월일이 있는 다섯 가족에 대한 데이터가 포함되어 있습니다. 이 데이터셋의 새로운 과제는 열 이름에 두 변수(dob, name)의 이름과 다른 변수(child, 값은 1 또는 2)의 값이 포함되어 있다는 것입니다. 이 문제를 해결하려면 다시 names_to에 벡터를 제공해야 하지만 이번에는 특수 ".value" 센티넬(sentinel)을 사용합니다. 이것은 변수의 이름이 아니라 pivot_longer()에 다른 작업을 수행하도록 지시하는 고유한 값입니다. 이는 일반적인 values_to 인수를 재정의하여 피벗된 열 이름의 첫 번째 구성 요소를 출력의 변수 이름으로 사용합니다.
household |>
pivot_longer(
cols = !family,
names_to = c(".value", "child"),
names_sep = "_",
values_drop_na = TRUE
)
#> # A tibble: 9 × 4
#> family child dob name
#> <int> <chr> <date> <chr>
#> 1 1 child1 1998-11-26 Susan
#> 2 1 child2 2000-01-29 Jose
#> 3 2 child1 1996-06-22 Mark
#> 4 3 child1 2002-07-11 Sam
#> 5 3 child2 2004-04-05 Seth
#> 6 4 child1 2004-10-10 Craig
#> # ℹ 3 more rows입력의 형태로 인해 명시적인 결측 변수(예: 자녀가 한 명뿐인 가족의 경우)가 강제로 생성되므로 다시 values_drop_na = TRUE를 사용합니다.
Figure 5.7 는 더 간단한 예제로 기본 아이디어를 보여줍니다. names_to에 ".value"를 사용하면 입력의 열 이름이 출력의 값과 변수 이름 모두에 기여합니다.
names_to = c(".value", "num")으로 피벗하면 열 이름이 두 구성 요소로 분할됩니다. 첫 번째 부분은 출력 열 이름(x 또는 y)을 결정하고 두 번째 부분은 num 열의 값을 결정합니다.
5.4 데이터 넓게 만들기
지금까지 값이 열 이름에 들어가 있는 일반적인 문제를 해결하기 위해 pivot_longer()를 사용했습니다. 다음으로 열을 늘리고 행을 줄여 데이터셋을 더 넓게 만들고 하나의 관측값이 여러 행에 걸쳐 있을 때 도움이 되는 pivot_wider()로 피벗(하하)해 보겠습니다. 이것은 야생에서 덜 흔하게 발생하는 것 같지만 정부 데이터를 다룰 때 많이 나타나는 것 같습니다.
환자 경험에 대한 데이터를 수집하는 메디케어 및 메디케이드 서비스 센터(Centers of Medicare and Medicaid services)의 데이터셋인 cms_patient_experience를 살펴보는 것으로 시작하겠습니다:
cms_patient_experience
#> # A tibble: 500 × 5
#> org_pac_id org_nm measure_cd measure_title prf_rate
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_1 CAHPS for MIPS… 63
#> 2 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_2 CAHPS for MIPS… 87
#> 3 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_3 CAHPS for MIPS… 86
#> 4 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_5 CAHPS for MIPS… 57
#> 5 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_8 CAHPS for MIPS… 85
#> 6 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_12 CAHPS for MIPS… 24
#> # ℹ 494 more rows연구의 핵심 단위는 조직이지만, 각 조직은 6개 행에 걸쳐 있으며 각 행은 조사 조직에서 수행된 측정 하나에 해당합니다. distinct()를 사용하여 measure_cd와 measure_title의 전체 값 집합을 볼 수 있습니다:
cms_patient_experience |>
distinct(measure_cd, measure_title)
#> # A tibble: 6 × 2
#> measure_cd measure_title
#> <chr> <chr>
#> 1 CAHPS_GRP_1 CAHPS for MIPS SSM: Getting Timely Care, Appointments, and In…
#> 2 CAHPS_GRP_2 CAHPS for MIPS SSM: How Well Providers Communicate
#> 3 CAHPS_GRP_3 CAHPS for MIPS SSM: Patient's Rating of Provider
#> 4 CAHPS_GRP_5 CAHPS for MIPS SSM: Health Promotion and Education
#> 5 CAHPS_GRP_8 CAHPS for MIPS SSM: Courteous and Helpful Office Staff
#> 6 CAHPS_GRP_12 CAHPS for MIPS SSM: Stewardship of Patient Resources이 두 열 중 어느 것도 특별히 좋은 변수 이름이 되지 않습니다. measure_cd는 변수의 의미를 암시하지 않으며 measure_title은 공백이 포함된 긴 문장입니다. 지금은 measure_cd를 새 열 이름의 소스로 사용하겠지만, 실제 분석에서는 짧고 의미 있는 자체 변수 이름을 만들고 싶을 수 있습니다.
pivot_wider()는 pivot_longer()와 반대되는 인터페이스를 가지고 있습니다. 새 열 이름을 선택하는 대신 값(values_from)과 열 이름(names_from)을 정의하는 기존 열을 제공해야 합니다:
cms_patient_experience |>
pivot_wider(
names_from = measure_cd,
values_from = prf_rate
)
#> # A tibble: 500 × 9
#> org_pac_id org_nm measure_title CAHPS_GRP_1 CAHPS_GRP_2
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… 63 NA
#> 2 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA 87
#> 3 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 4 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 5 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 6 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> # ℹ 494 more rows
#> # ℹ 4 more variables: CAHPS_GRP_3 <dbl>, CAHPS_GRP_5 <dbl>, …출력이 아주 맞아 보이지는 않습니다. 여전히 각 조직에 대해 여러 행이 있는 것 같습니다. 그 이유는 pivot_wider()에 어떤 열(들)이 각 행을 고유하게 식별하는 값을 가지고 있는지 알려줘야 하기 때문입니다. 이 경우 "org"로 시작하는 변수들입니다:
cms_patient_experience |>
pivot_wider(
id_cols = starts_with("org"),
names_from = measure_cd,
values_from = prf_rate
)
#> # A tibble: 95 × 8
#> org_pac_id org_nm CAHPS_GRP_1 CAHPS_GRP_2 CAHPS_GRP_3 CAHPS_GRP_5
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 0446157747 USC CARE MEDICA… 63 87 86 57
#> 2 0446162697 ASSOCIATION OF … 59 85 83 63
#> 3 0547164295 BEAVER MEDICAL … 49 NA 75 44
#> 4 0749333730 CAPE PHYSICIANS… 67 84 85 65
#> 5 0840104360 ALLIANCE PHYSIC… 66 87 87 64
#> 6 0840109864 REX HOSPITAL INC 73 87 84 67
#> # ℹ 89 more rows
#> # ℹ 2 more variables: CAHPS_GRP_8 <dbl>, CAHPS_GRP_12 <dbl>이것이 우리가 찾고 있는 출력을 제공합니다.
5.4.1 pivot_wider()는 어떻게 작동하나요?
pivot_wider()가 어떻게 작동하는지 이해하기 위해 다시 아주 간단한 데이터셋으로 시작해 봅시다. 이번에는 id가 A와 B인 두 명의 환자가 있고 환자 A에 대해 세 번, 환자 B에 대해 두 번의 혈압 측정이 있습니다:
df <- tribble(
~id, ~measurement, ~value,
"A", "bp1", 100,
"B", "bp1", 140,
"B", "bp2", 115,
"A", "bp2", 120,
"A", "bp3", 105
)value 열에서 값을 가져오고 measurement 열에서 이름을 가져올 것입니다:
df |>
pivot_wider(
names_from = measurement,
values_from = value
)
#> # A tibble: 2 × 4
#> id bp1 bp2 bp3
#> <chr> <dbl> <dbl> <dbl>
#> 1 A 100 120 105
#> 2 B 140 115 NA프로세스를 시작하기 위해 pivot_wider()는 먼저 행과 열에 무엇이 들어갈지 파악해야 합니다. 새 열 이름은 measurement의 고유 값이 될 것입니다.
기본적으로 출력의 행은 새 이름이나 값에 들어가지 않는 모든 변수에 의해 결정됩니다. 이것들을 id_cols라고 합니다. 여기에는 열이 하나만 있지만 일반적으로는 얼마든지 있을 수 있습니다.
그런 다음 pivot_wider()는 이 결과들을 결합하여 빈 데이터 프레임을 생성합니다:
그런 다음 입력의 데이터를 사용하여 모든 결측값을 채웁니다. 이 경우 환자 B에 대한 세 번째 혈압 측정이 없으므로 출력의 모든 셀에 해당하는 입력 값이 있는 것은 아니며 해당 셀은 결측 상태로 유지됩니다. Chapter 18 에서 pivot_wider()가 결측값을 “만들” 수 있다는 이 아이디어를 다시 다룰 것입니다.
또한 입력에 출력의 한 셀에 해당하는 여러 행이 있는 경우 어떻게 되는지 궁금할 수 있습니다. 아래 예제에는 id “A”와 measurement “bp1”에 해당하는 두 개의 행이 있습니다:
df <- tribble(
~id, ~measurement, ~value,
"A", "bp1", 100,
"A", "bp1", 102,
"A", "bp2", 120,
"B", "bp1", 140,
"B", "bp2", 115
)이것을 피벗하려고 시도하면 리스트 열(list-columns)이 포함된 출력을 얻게 되는데, 이에 대해서는 Chapter 23 에서 더 자세히 배울 것입니다:
df |>
pivot_wider(
names_from = measurement,
values_from = value
)
#> Warning: Values from `value` are not uniquely identified; output will contain
#> list-cols.
#> • Use `values_fn = list` to suppress this warning.
#> • Use `values_fn = {summary_fun}` to summarise duplicates.
#> • Use the following dplyr code to identify duplicates.
#> {data} |>
#> dplyr::summarise(n = dplyr::n(), .by = c(id, measurement)) |>
#> dplyr::filter(n > 1L)
#> # A tibble: 2 × 3
#> id bp1 bp2
#> <chr> <list> <list>
#> 1 A <dbl [2]> <dbl [1]>
#> 2 B <dbl [1]> <dbl [1]>아직 이런 종류의 데이터를 다루는 방법을 모르기 때문에 경고의 힌트를 따라 문제가 어디에 있는지 파악하고 싶을 것입니다:
데이터에 무엇이 잘못되었는지 파악하고 근본적인 손상을 복구하거나 그룹화 및 요약 기술을 사용하여 행과 열 값의 각 조합에 단일 행만 있도록 하는 것은 여러분의 몫입니다.
5.5 요약
이 장에서는 깔끔한 데이터, 즉 변수는 열에 있고 관측값은 행에 있는 데이터에 대해 배웠습니다. 깔끔한 데이터는 대부분의 함수가 이해하는 일관된 구조이기 때문에 tidyverse에서 작업하기가 더 쉬우며, 주된 과제는 어떤 구조로 받든 데이터를 깔끔한 형식으로 변환하는 것입니다. 이를 위해 깔끔하지 않은 많은 데이터셋을 정리할 수 있는 pivot_longer()와 pivot_wider()에 대해 배웠습니다. 여기서 제시한 예제는 vignette("pivot", package = "tidyr")에서 선택한 것이므로 이 장에서 도움이 되지 않는 문제에 직면하면 해당 비네트가 다음에 시도해 볼 수 있는 좋은 장소입니다.
또 다른 과제는 주어진 데이터셋에 대해 더 긴 버전이나 더 넓은 버전 중 하나를 “깔끔한” 것이라고 라벨링하는 것이 불가능할 수 있다는 것입니다. 이는 부분적으로 깔끔한 데이터에 대한 우리의 정의를 반영하는 것인데, 우리는 깔끔한 데이터가 각 열에 하나의 변수를 갖는다고 말했지만 실제로 변수가 무엇인지는 정의하지 않았습니다(그리고 그렇게 하기는 놀라울 정도로 어렵습니다). 실용적으로 생각해서 분석을 가장 쉽게 만드는 것이 변수라고 말하는 것은 전적으로 괜찮습니다. 따라서 계산을 수행하는 방법을 알아내는 데 막힌다면 데이터 구성을 바꾸는 것을 고려해 보세요. 필요에 따라 어지럽히고(untidy), 변형하고, 다시 정리하는(re-tidy) 것을 두려워하지 마세요!
이 장을 즐겼고 기본 이론에 대해 더 알고 싶다면 Journal of Statistical Software에 게시된 Tidy Data 논문에서 역사와 이론적 토대에 대해 자세히 알아볼 수 있습니다.
이제 상당한 양의 R 코드를 작성하고 있으므로 코드를 파일과 디렉터리로 구성하는 것에 대해 더 배울 때입니다. 다음 장에서는 스크립트와 프로젝트의 이점과 삶을 더 쉽게 만들기 위해 제공하는 많은 도구 중 일부에 대해 모두 배울 것입니다.
노래는 2000년 어느 시점에 상위 100위 안에 들었다면 포함되며, 등장 후 최대 72주 동안 추적됩니다.↩︎
Chapter 18 에서 이 아이디어를 다시 다룰 것입니다.↩︎