5  데이터프레임 탐색

4 장에서 R의 모든 기본 자료형과 자료구조에 대한 여행을 마쳤다. 데이터 과학에서 수행하는 모든 작업은 4 장에서 소개한 도구를 조작하는 것이 된다. 하지만, 거의 대부분의 데이터 과학 프로젝트에서 진정한 스타는 데이터프레임(dataframe)이다. CSV 파일에서 정보를 불러와 생성한 테이블을 지칭한다. 이번 장에서는 데이터프레임으로 작업하는 방법에 대해 좀 더 상세히 살펴본다.

5.1 행과 열 추가

데이터프레임의 칼럼은 벡터라는 것을 배웠다. 따라서, 데이터는 칼럼에서 자료형의 일관성을 유지해야 한다. 이를테면, 칼럼을 새로 추가하려면 벡터를 새로 만드는 것부터 시작한다.

R

age를 칼럼으로 다음과 같이 추가한다.

데이터프레임의 행의 개수와 다른 개수를 갖는 age 벡터를 추가하게 되면 추가되지 않고 오류가 발생된다는 점에 주의한다.

정상 동작이 되지 않는 이유가 무엇일까? R은 테이블의 모든 행마다 신규 칼럼에서도 원소 하나가 있길 원한다.

그래서, 정상 동작하려면 nrow(cats) = length(age)이 되어야 한다. cats 내용을 새로운 데이터프레임으로 덮어써보자.

이제 행을 추가하면 어떻게 될까? 이미 데이터프레임의 행이 리스트라는 사실을 알고 있다.

5.2 요인

살펴볼 것이 하나 더 있다. 범주형 자료 처리를 위한 요인(factor)에서 각기 다른 값을 범주 수준(level)이라고 한다. 요인형 “coat” 변수는 수준이 3으로 구성된다. “black”, “calico”, “tabby”. R은 세 가지 수준 중 하나와 매칭되는 값만 받아들인다. 완전 새로운 값을 추가하게 되면, 추가되는 신규 값은 NA가 된다.

경고 메시지를 통해서 coat 요인변수에 “tortoiseshell” 값을 추가하는 데 성공하지 못했다고 알려준다. 하지만, 3.3 (숫자형), TRUE (논리형), 9 (숫자형)은 모두 weight, likes_string, age 변수에 성공적으로 추가된다. 왜냐하면 이 변수들이 요인형이 아니기 때문이다. “tortoiseshell”을 coat 요인변수에 성공적으로 추가하려면 요인의 수준(level)로 “tortoiseshell”을 추가하면 된다.

대안으로, 요인형 벡터를 문자형 벡터로 변환시키면 된다. 요인변수의 범주를 잃게 되지만, 요인 수준을 조심스럽게 다룰 필요 없이 칼럼에 추가하고자 하는 임의의 단어를 추가할 수 있다.

도전과제
  1. cats$age 벡터에 7을 곱해서 human_age 벡터를 생성하자.
  2. human_age를 요인형으로 변환하자.
  3. as.numeric() 함수를 사용해서 human_age 벡터를 다시 숫자형 벡터로 변환한다. 이제 7로 나눠서 원래 고양이 나이로 되돌리자. 무슨 일이 생겼는지 설명하자.
  1. human_age <- cats$age * 7
  2. human_age <- factor(human_age). as.factor(human_age)도 잘 동작한다.
  3. as.numeric(human_age)을 실행하면 1 2 3 4 4이 된다.

왜냐하면 요인형 변수는 정수형(여기서 1:4)으로 자료를 저장하기 때문이다. 정수 라벨과 연관된 값은 여기서 28, 35, 56, 63이다. 요인형 변수를 숫자형 벡터로 변환시키면 라벨이 아니라 그 밑단의 정수를 반환시킨다. 원래 숫자를 원하는 경우, human_age를 문자형 벡터로 변환시키고 나서 숫자형 벡터로 변환시키면 된다. (왜 이 방식은 정상 동작할까?) 실수로 숫자만 담긴 칼럼 어딘가에 문자가 포함된 csv 파일로 작업할 때 이런 일이 실제로 종종 일어난다. 데이터를 불러올 때 stringsAsFactors=FALSE 설정을 잊지 말자.

5.3 행 제거

이제 데이터프레임에 행과 열을 추가하는 방법을 알게 되었다. 하지만, 데이터프레임에 “tortoiseshell” 고양이를 처음으로 추가하면서 우연히 쓰레기 행을 추가시켰다. 데이터프레임에서 문제가 되는 행을 마이너스 연산자를 사용해서 제거하자.

-4, 다음에 아무것도 지정하지 않아서 4번째 행 전체를 제거함에 주목한다. 주목: 벡터 내부에 행 다수를 넣어 한번에 행을 제거할 수도 있다: cats[c(-4,-5), ] 대안으로, NA 값을 갖는 모든 행을 제거한다. 출력결과를 cats에 다시 대입하여 변경사항이 데이터프레임에 영구히 남도록 조치한다.

5.4 칼럼 제거

데이터프레임의 칼럼도 제거할 수 있다. “age” 칼럼을 제거하고자 한다면 어떻게 해야 할까? 변수명과 변수 인덱스, 두 가지 방식으로 칼럼을 제거할 수 있다.

,-4 앞에 아무것도 없는 것에 주목한다. 이는 모든 행을 간직한다는 의미를 갖는다. 대안으로, 색인명을 사용해서 컬럼을 제거할 수도 있다.

5.5 덧붙이기

데이터프레임에 데이터를 추가할 때 기억할 것은 칼럼은 벡터, 행은 리스트라는 사실이다. rbind() 함수를 사용해서 데이터프레임 두 개를 본드로 붙이듯이 결합시킬 수 있다.

하지만 이제 행 이름이 불필요하게 복잡해졌다. 행 이름을 제거하면 R이 자동으로 순차적인 이름을 다시 붙여준다.

도전과제

다음 구문을 사용해서 R 내부에서 직접 데이터프레임을 새로 만들 수 있다.

다음 정보를 갖는 데이터프레임을 직접 제작해 보자.

  • 이름(first name)
  • 성(last name)
  • 좋아하는 숫자

rbind를 사용해서 옆사람을 항목에 추가한다. 마지막으로 cbind() 함수를 사용해서 “지금이 커피 시간인가요?”라는 질문의 답을 칼럼으로 추가한다.

5.6 현실적인 예제

지금까지 고양이 데이터를 가지고 데이터프레임 조작에 대한 기본적인 사항을 살펴봤다. 이제 학습한 기술을 사용해서 좀 더 현실적인 데이터셋을 다뤄보자. 앞에서 다운로드받은 gapminder 데이터셋을 불러오자.

gapminder <- read.csv("data/gapminder_data.csv")
데이터셋 설명
  • 흔히 맞닥뜨리는 또 다른 유형의 파일이 탭으로 구분된 파일(.tsv)이다. 탭을 구분자로 명세하는데, "\\t"을 사용하고, read.delim() 함수로 불러온다.

  • download.file() 함수를 사용해서 파일을 인터넷으로부터 직접 본인 컴퓨터 폴더로 다운로드할 수 있다. read.csv() 함수를 실행해서 다운로드받은 파일을 읽어온다. 예를 들어,

  • 대안으로, read.csv() 함수 내부에 파일 경로를 웹 주소로 치환해서 인터넷에서 직접 파일을 불러올 수도 있다. 이런 경우 로컬 컴퓨터에 CSV 파일이 전혀 저장되지 않는다는 점을 주의한다. 예를 들어,
gapminder <- read.csv("https://raw.githubusercontent.com/swcarpentry/r-novice-gapminder/main/episodes/data/gapminder_data.csv")
  • readxl 패키지를 사용해서, 엑셀 스프레드시트를 평범한 텍스트로 변환하지 않고 직접 불러올 수도 있다.

gapminder 데이터셋을 좀 더 살펴보자. 항상 가장 먼저 해야 하는 작업은 str 명령어로 데이터가 어떻게 생겼는지 확인하는 것이다.

typeof() 함수로 데이터프레임 칼럼 각각을 면밀히 조사할 수도 있다.

데이터프레임 차원에 대한 정보를 얻어낼 수도 있다. str(gapminder) 실행결과 gapminder 데이터프레임에 관측점 1704개, 변수 6개가 있음을 상기한다. 다음 코드 실행결과는 무엇일까? 그리고 왜 그렇게 되는가?

공정한 추측은 아마도 데이터프레임 길이가 행의 길이(1704)라고 보는 것이다. 하지만, 이번에는 다르다. 데이터프레임은 벡터와 요인으로 구성된 리스트라는 사실을 기억하자.

length() 함수가 6을 제시하는 이유는 gapminder가 6개 칼럼을 갖는 리스트로 만들어졌기 때문이다. 데이터셋에서 행과 열 숫자를 얻는 데 다음 함수를 던져보자.

혹은 한번에 보려면 다음과 같이 한다.

또한, 모든 칼럼의 칼럼명이 무엇인지 파악하고자 하면 다음과 같이 질문을 던진다.

현 단계에서, R이 제시하는 구조가 우리의 직관이나 예상과 부합되는지 묻어보는 것이 중요하다. 각 칼럼에 대한 기본 자료형은 이해가 되는가? 만약 납득이 가지 않는다면, 후속 작업에서 나쁜 놀라운 사실로 전환되기 전에 문제를 해결해야 한다. 문제를 해결하는 데, R이 데이터를 이해하는 방법과 데이터를 기록할 때 엄격한 일관성(strict consistency)의 중요성에 관해 학습한 것을 활용한다.

자료형과 자료구조가 타당해 보이게 되면, 데이터를 제대로 파고들 시간이 되었다. gapminder 데이터의 처음 몇 줄을 살펴보자.

도전과제

데이터 마지막 몇 줄, 중간 몇 줄을 점검하는 것도 좋은 습관이다. 그런데 어떻게 점검할 수 있을까?

중간 몇 줄을 찾아보는 것은 너무 어렵지 않지만, 임의로 몇 줄을 추출할 수도 있다. 어떻게 할 수 있을까?

마지막 몇 줄을 점검하려면, R에 내장된 함수를 사용하면 상대적으로 간단하다.

데이터가 온전한지(혹은 관점에 따라 데이터가 온전하지 않은지)를 점검하기 위해 몇 줄을 추출할 수 있을까?

팁: 몇 가지 방법이 존재한다.

중첩 함수(또 다른 함수에 인자로 전달되는 함수)를 사용한 해법도 있다. 새로운 개념처럼 들리지만, 사실 이미 사용하고 있다. my_dataframe[rows, cols] 명령어는 데이터프레임을 화면에 뿌려준다. 데이터프레임에 행이 얼마나 많은지 알지 못하는데 어떻게 마지막 행을 뽑아낼 수 있을까? R에 내장된 함수가 있다. (의사) 난수를 얻어보는 것은 어떨까? R은 난수추출 함수도 갖추고 있다.

분석 결과를 재현 가능하게 확실히 만들려면, 코드를 스크립트 파일에 저장해서 나중에 다시 볼 수 있어야 한다.

도전과제

file -> new file -> R script로 가서, gapminder 데이터셋을 불러오는 R 스크립트를 작성한다. scripts/ 디렉토리에 저장하고 버전 제어 시스템에도 추가한다. 인자로 파일 경로명을 사용해서 source() 함수를 사용해서 스크립트를 실행하라. (혹은 RStudio “source” 버튼을 누른다)

scripts/load-gapminder.R 파일에 담긴 내용물은 다음과 같다.

download.file("https://raw.githubusercontent.com/swcarpentry/r-novice-gapminder/main/episodes/data/gapminder_data.csv", destfile = "data/gapminder_data.csv")
gapminder <- read.csv(file = "data/gapminder_data.csv")

스크립트를 실행하면 데이터를 gapminder 변수에 적재한다.

source(file = "scripts/load-gapminder.R")
도전과제

str(gapminder) 출력결과를 다시 불러오자. 이번에는 gapminder 데이터에 대해 str() 함수가 출력하는 모든 것이 의미하는 바를 설명한다. 지금까지 학습한 요인, 리스트와 벡터뿐만 아니라 colnames(), dim() 같은 함수도 동원한다. 이해하지 못한 부분이 있다면, 주변 동료와 상의한다!

gapminder 객체는 다음 칼럼을 갖는 데이터프레임이다. - countrycontinent 변수는 요인형 벡터 - year 변수는 정수형 벡터 - pop, lifeExp, gdpPercap 변수는 숫자형 벡터

5.7 요약

데이터 분석을 수행할 때 가장 중요한 자료 구조는 데이터프레임이다. 데이터프레임은 행과 열로 구성된 테이블 형태의 데이터를 저장하는 데 사용되며, 각 열은 벡터로 구성되고 각 행은 관측치를 나타낸다. 데이터프레임에 새로운 행이나 열을 추가할 때는 데이터의 일관성을 유지하는 것이 중요하다.

데이터프레임의 열은 각각 서로 다른 자료형을 가질 수 있지만, 한 열 내에서는 모든 데이터가 동일한 자료형을 가져야 하며 필요한 경우 자료형 변환을 수행해야 한다. 특히 문자열로 구성된 열을 처리할 때는 요인형(factor)으로 변환하는 것이 효과이다.

데이터프레임에서 필요 없는 행이나 열은 제거할 수 있다. 행을 제거할 때는 행 번호를 이용하거나 조건에 맞는 행을 선택하여 제거할 수 있으며, 열을 제거할 때는 열 이름이나 열 번호를 사용한다. 또한 두 개 이상의 데이터프레임을 병합하여 새로운 데이터프레임을 생성할 수도 있다.

실제 데이터 분석에서는 데이터의 구조와 내용을 파악하는 것이 매우 중요하다. 왜냐하면 처음 보는 데이터로 어떻게 구성되었는지 파악하는 것이 먼저다. str(), head(), tail(), summary() 등의 함수를 활용하여 데이터의 개요를 살펴보고, 각 변수의 자료형과 값의 분포를 확인해야 한다. 또한 데이터에 결측치나 이상치가 있는지 검토하고, 상황에 맞게 적절히 처리해야 한다.