4  자료구조

/index{자료구조}

R 프로그래밍에서 데이터를 다루는 기본적인 자료구조에 대해 살펴본다. CSV 파일 같은 외부 데이터를 R로 불러오는 것부터 시작해서, R에서 사용되는 실수형, 정수형, 복소수형, 논리형, 문자형 등 다양한 자료형의 특성을 이해하고, 기본 자료형으로 구성되는 벡터, 요인, 리스트, 데이터프레임 등의 자료구조가 서로 어떻게 연관되어 있는지 살펴본다. 특히 범주형 데이터를 표현하는 요인형에 대해서도 다루며, 이 과정에서 실제 데이터 분석에서 마주치는 다양한 유형의 데이터를 R이 어떻게 유연하게 처리할 수 있는지 기본기를 익히게 된다.

R의 가장 강력한 기능 중 하나는 표 형식 데이터를 다룰 수 있는 능력이다. 이미 스프레드쉬트나 CSV 파일을 갖고 있을 수 있다. 로컬 컴퓨터에서 실습하고자 한다면, data/ 디렉토리에 feline-data.csv 파일을 생성하면서 시작해 보자. webr을 사용하여 실습한다면 data/ 디렉토리 없이 실습한다.

새로 생성한 feline-data.csv 파일 내부는 다음과 같다.

coat,weight,likes_string
calico,2.1,1
black,5.0,0
tabby,3.2,1
R에서 텍스트 파일 편집

대안으로, data/feline-data.csv 파일을 생성하는 데 나노 편집기(Nano)를 사용하거나, RStudio File -> New File -> Text File 메뉴를 사용할 수 있다.

다음 명령어를 통해서 R로 데이터를 불러온다.

read.table 함수를 사용해서 텍스트 파일에 저장된 표 형식 데이터를 불러오는 데 사용한다. 표 형식 데이터는 CSV (csv = 콤마로 구분된 값) 같은 구분 문자(punctuation characters)로 칼럼이 구분된다. .csv 파일에서 데이터를 구분하는 데 가장 일반적으로 사용되는 구분 문자가 탭과 콤마이다. R에서 사용의 편리성을 위해 read.table 함수에 두 가지 다른 버전 함수를 제공한다. read.csv가 콤마로 구분되는 데이터를 읽어들이는 데 사용되고, read.delim 함수가 탭으로 구분되는 데이터를 읽어오는 데 사용된다. read.csv 함수가 둘 중에 더 많이 사용된다. 필요한 경우 read.csv, read.delim 함수 모두 구분 문자를 오버라이드해서 사용하는 것도 가능하다.

데이터프레임에 $ 연산자를 사용해서 칼럼을 뽑아내면서 데이터셋을 바로 탐색하기 시작하는 것도 가능하다.

칼럼에 다른 연산자를 적용할 수도 있다:

이번에 다음은 어떤가?

상기 문장에서 발생된 것을 이해하는 것이 R로 데이터 분석을 성공적으로 수행하는 데 중요하다.

4.1 자료형

2.1 더하기 "black"이 말이 되지 않기 때문에 마지막 문장이 오류를 뱉어낼 것이라고 추측한다면, 제대로 이해하고 있는 것이다. 이미 자료형(data type)이라고 불리는 프로그래밍의 중요한 개념에 대한 직관을 갖추고 있는 것이다. 데이터 자료형이 무엇인지 다음을 통해 물어볼 수 있다.

다섯 가지 주요 자료형이 있다. 실수형(double), 정수형(integer), 복소수형(complex), 논리형(logical), 문자형(character).

분석이 얼마나 복잡해지냐에 관계없이, R에서 모든 데이터는 이러한 기본 자료형 중 하나로 해석된다. 이러한 엄격함이 정말로 중요한 결과를 낳게 된다.

사용자가 또 다른 고양이에 대한 상세 내용을 추가했고, 추가 정보는 data/feline-data_v2.csv 파일에 저장되어 있다.

# file.show("data/feline-data-v2.csv")
file.show("feline-data-v2.csv")
coat,weight,likes_string
calico,2.1,1
black,5.0,0
tabby,3.2,1
tabby,2.3 or 2.4,1

앞서와 마찬가지로 새로운 고양이 데이터를 불러와서 weight 칼럼의 데이터 자료형이 무엇인지 확인한다.

이러면 안 되는데, 고양이 몸무게가 더 이상 실수형 자료형이 아니다! 앞서 수행했던 동일한 수학 연산을 취하게 되면 문제에 봉착한다.

무슨 일이 일어난 걸까? R이 csv 파일을 불러올 때, 칼럼에 모든 것이 동일한 자료형이 되는 것을 요구한다; 칼럼에 모든 원소가 실수형으로 인식되지 않으면, 칼럼에 어떤 원소도 실수형이 될 수 없다. 고양이 데이터를 불러온 테이블을 데이터프레임(data.frame)이라고 부르고, 자료구조(data structure)로 불리는 첫 번째 사례가 된다. 즉, 자료구조는 기본 자료형에서 R이 생성할 줄 아는 구조가 된다.

class 함수를 호출해서 데이터프레임인지를 알 수 있다.

R에서 데이터를 성공적으로 사용하려면 기본 자료구조가 무엇이고 어떻게 동작하는지 이해할 필요가 있다. 지금으로서는 추가된 마지막 줄을 제거하고 좀 더 살펴보도록 하자:

feline-data.csv:

coat,weight,likes_string
calico,2.1,1
black,5.0,0
tabby,3.2,1

RStudio로 다시 돌아와서,

4.2 벡터와 자료형 강제변환

강제변환을 보다 잘 이해하기 위해서, 또 다른 자료구조(벡터(vector))를 만나보자.

R에서 벡터는 본질적으로 무언가 순서를 갖는 리스트로, 특별한 성질을 갖는데 벡터에 모든 것은 동일한 자료형을 갖는다는 점이다. 자료형을 지정하지 않게 되면, 기본 디폴트 설정은 논리형(logical)이 된다. 자료형에 관계없이 공벡터를 선언할 수 있다. 자료가 벡터인지 다음과 같이 확인한다.

상기 명령어로부터 다소 암호스러운 출력결과가 나오는데 해당 벡터에서 발견된 기본 자료형은 이 경우 chr 문자; 벡터에 나와 있는 숫자는 벡터의 인덱스로 이 경우 [1:3]; 그리고 벡터에 실제로 들어있는 몇 가지 예로, 이 경우 빈 문자열이 된다. 유사하게 다음을 실행하게 되면,

cats$weight도 벡터임을 알 수 있다. R 데이터프레임으로 불러온 데이터 칼럼은 모두 벡터다. 이러한 연유로 칼럼에 있는 모든 원소는 동일한 자료형을 갖도록 강제되는 이유가 된다.

토론 주제

왜 R에서는 데이터 칼럼에 무엇을 넣는지에 대해서 고집스럽게 주장할까? 이러한 점은 어떻게 우리에게 도움이 될까?

칼럼에 모든 것을 동일하게 둠으로써, 데이터에 관해 단순한 가정을 할 수 있게 한다; 만약 칼럼의 첫 번째 입력값이 숫자라면, 모든 입력값을 숫자로 해석할 수 있게 되고, 그렇게 함으로써 모든 것을 확인할 필요가 없게 된다. 깨끗한 데이터(clean data)라고 사람들이 회자할 때, 사람들이 의미하는 것이 바로 이러한 일관성이다. 장기적으로 엄격한 일관성이 R에서 우리 삶을 풍요롭고 수월하게 만들 것이다.

결합 함수(c())를 사용해서 벡터를 생성할 수 있다.

지금까지 학습한 것을 바탕으로, 다음 문장은 어떤 결과를 출력하게 될까?

이것을 자료형 강제변환(type coercion)이라고 부른다. 이것이 많은 놀라움의 원천이고, 왜 기본 자료형에 대해 인지하고 있어야 하는 이유가 되며, R이 해석하는 방식도 알아야 한다. R에서 혼합된 자료형(상기 예제는 숫자와 문자)의 경우 단 하나의 벡터로 변환시킬 때, 모든 자료를 동일한 자료형으로 강제 변환시킨다. 다음을 생각해 보자.

강제 변환 규칙은 다음과 같이 적용된다: 논리형 -> 정수형 -> 숫자형 -> 복소수형 -> 문자형. 여기서 -> 표현은 다음으로 변환된다로 읽힌다. 이런 자동 변환 규칙에 거슬러 자료형을 강제로 변환시키려면 as. 함수를 사용한다:

R이 기본 자료형을 다른 자료형으로 강제 변환할 때, 놀라운 일이 생겨난다! 자료형 강제변환에 대한 핵심사항은 차치하고 중요한 점은 다음과 같다. 만약 내 데이터가 생각했던 것과 다르게 보인다면, 자료형 강제변환이 원인일 가능성이 높다. 벡터와 데이터프레임의 칼럼 자료형이 동일하도록 확실히 하라. 그렇지 않으면 끔찍한 놀라운 경험을 하게 될 것이다!

하지만, 경우에 따라서는 자료형 강제변환이 매우 유용할 수도 있다! 예를 들어, cats 데이터프레임의 likes_string 칼럼은 숫자형이지만, 1과, 0이 실제로 TRUEFALSE를 표현한다는 것을 알고 있다. 이 경우 두 상태(TRUE 혹은 FALSE)를 갖는 논리형 자료형을 사용해야 한다. as.logical 함수를 사용해서 칼럼을 논리형(logical)으로 ’강제변환(coerce)’시킨다.

결합 함수(c())는 기존 벡터에 무언가 추가하는 역할을 수행한다.

숫자 순열도 생성할 수 있다.

벡터에 관해 궁금한 점도 물어볼 수 있다.

마지막으로, 벡터의 각 원소에 명칭을 부여하는 것도 가능하다:

도전과제

1부터 26까지의 숫자를 갖는 벡터를 생성하면서 시작해 보자. 생성한 벡터에 2를 곱해서 다시 자신에게 할당한다. 벡터에 A부터 Z까지 이름을 부여한다. (힌트: LETTERS라는 내장벡터가 있다.)

4.3 데이터프레임

데이터프레임의 칼럼이 벡터라고 앞에서 언급했다.

말이 된다. 다음은 어떤가?

4.4 요인

또 다른 중요한 자료구조가 요인(factor)이다. 요인은 보통 문자 데이터처럼 생겼다. 하지만, 일반적으로 범주형 정보를 나타내는 데 사용된다. 예를 들어, 연구 중인 모든 고양이에 대한 색상을 문자열 벡터로 만들어보자:

문자열 벡터를 요인형으로 바꾸면 다음과 같다.

이제 R은 데이터에 3가지 가능한 범주가 있음을 파악하게 되었다. 하지만, 놀라운 것도 함께 수행했다; 문자열을 출력하는 대신에, 숫자가 대량으로 출도 되었다. R은 내부적으로 사람이 읽을 수 있는 범주를 숫자 인덱스로 치환시킨다. 이런 기능은 대다수 통계 계산에서 범주형 데이터를 숫자형으로 표현되는 기능을 활용하기 때문에 꼭 필요하다.

도전과제

cats 데이터프레임에 요인형 칼럼이 있나요? 요인형 칼럼의 이름은 무엇인가요? ?read.csv 명령어를 사용해서 텍스트 칼럼을 요인형 대신에 문자형으로 그대로 유지시키는 방법을 찾아본다. 그리고 나서 cats 데이터프레임의 요인이 실제로 문자벡터임을 확인하는 명령문을 작성하시오.

해법으로 stringAsFactors 인자를 사용하면 된다.:

또 다른 해법은 colClasses 인자를 사용해서 칼럼을 좀 더 면밀히 제어하는 것이다.

주의: 도움말 파일이 이해하기 어렵다는 학생이 다수 있다; 도움말 파일을 이해하기 어렵다는 것이 일반적이라서, 확신하지는 못하더라도, 문맥에 기초하여 최대한 추측하도록 용기를 주도록 한다.

모형 함수에서 기준 수준(baseline level)이 무엇인지 파악하는 것이 중요하다. 요인의 첫 번째 범주로 가정하지만, 기본 디폴트는 알파벳순으로 정해지게 되어 있다. 수준을 다음과 같이 지정해서 변경할 수 있다.

상기 경우 “control”를 1로, “case”를 2로 명시적으로 지정하도록 했다. 이러한 지정이 통계 모형 결과를해석하는 데 매우 중요하다.

4.5 리스트

데이터 과학자로서 알고 있어야 하는 또 다른 자료구조가 리스트(list)다. 리스트는 다른 자료형과 비교하여 몇 가지 점에서 더 단순하다. 왜냐하면 원하는 무엇이든 넣을 수 있기 때문이다.

데이터프레임에서 다소 놀라운 것을 이제 이해할 수 있다. 다음을 실행하게 되면 무슨 일이 발생될까?

데이터프레임은 ‘내부적으로(under the hood)’ 리스트라는 것을 알 수 있다 - 이유는 데이터프레임이 실제로 벡터와 요인으로 구성된 리스트이기 때문이다. 벡터와 요인으로 뒤섞인 칼럼을 붙잡아 두려면, 데이터프레임은 모든 칼럼을 유사한 표에 담을 수 있는 벡터보다 더 유연할 필요가 있다. 다른 말로, data.frame은 모든 벡터가 동일한 길이를 갖는 특별한 리스트로 정의할 수 있다.

cats 사례에서는 정수형, 숫자형, 논리형 변수로 구성된다. 이미 살펴봤듯이, 데이터프레임 각 칼럼은 벡터다.

각 행은 다른 변수의 관측점(observation)으로 그 자체로 데이터프레임이다. 따라서, 서로 다른 자료형을 갖는 원소로 구성된다.

도전과제

데이터프레임에서 변수와 관측점과 원소를 호출하는 미묘하지만 다른 방식이 존재한다. - cats[1] - cats[[1]] - cats$coat - cats["coat"] - cats[1, 1] - cats[, 1] - cats[1, ]

상기 예제를 시도해보고, 각각이 반환하는 것을 설명해 본다. 힌트: typeof() 함수를 사용해서 각각의 경우에 반환되는 것을 꼼꼼히 살펴본다.

데이터프레임을 벡터로 구성된 리스트로 간주할 수 있다. 단일 꺾쇠 [1]은 리스트의 첫 번째 원소를 리스트로 반환한다. 이번 경우, 데이터프레임의 첫 번째 칼럼이 된다.

이중 꺾쇠 [[1]]은 리스트의 원소 내용물을 반환한다. 이번 경우, 리스트가 아닌 요인형 벡터로 첫 번째 칼럼 내용물을 반환한다.

명칭으로 항목을 끄집어내는 데 $ 기호를 사용한다. _coat_가 데이터프레임의 첫 번째 칼럼으로 요인형 벡터가 반환된다.

["coat"] 방식은 칼럼 인덱스를 명칭으로 바꾸고 동시에 꺾쇠를 사용한 경우다. 예제 1과 마찬가지로 반환되는 객체는 리스트가 된다.

단일 꺾쇠를 사용했는데 이번에는 행과 열의 좌표도 넣어 전달했다. 반환되는 객체는 첫 번째 행, 첫 번째 열에 교차하는 값이 된다. 반환되는 객체는 정수형이지만, 요인형 벡터의 일부분이라 정수값과 연관된 라벨 “calico”도 함께 출력한다.

앞선 예제와 마찬가지로 꺾쇠를 하나만 사용했고, 행과 열 좌표도 전달했다. 행좌표를 지정하지 않은 경우, R에서 결측값은 해당 칼럼 벡터의 모든 원소로 해석된다.

다시 한번, 꺾쇠를 하나만 사용했고, 행과 열 좌표도 전달했다. 칼럼 좌표가 지정되어 있지 않기 때문에, 첫 번째 행의 모든 값을 포함하는 리스트가 반환된다.

4.6 행렬

마지막으로 중요한 자료형이 행렬이다. 0으로 가득 찬 행렬을 다음과 같이 선언한다.

다른 자료구조와 마찬가지로, 행렬에 질문을 다음과 같이 던질 수 있다:

도전과제

length(matrix_example) 실행결과는 어떻게 나올까? 시도해보자. 생각한 것과 일치하는가? 왜 그런가/ 왜 그렇지 않는가?

행렬은 차원 속성이 추가된 벡터라서, length() 함수는 행렬의 전체 원소 개수를 반환시킨다.

도전과제

또 다른 행렬을 만들어 보자. 이번에는 1:50 숫자를 담고 있는 칼럼이 5, 행이 10인 행렬이다. matrix() 함수로 칼럼 기준으로 혹은 행 기준으로 채울 수 있나요? 행과 열을 바꿔 변경할 수 있는 방법을 찾아보자. (힌트: matrix 도움말 문서를 참조한다!)

도전과제

이번 워크샵에서 다룬 각 섹션별 문자벡터를 포함하는 길이 2를 갖는 리스트를 생성하시오.

  • 자료형
  • 자료구조

지금까지 살펴본 자료형과 자료구조를 명칭으로 갖는 문자 벡터를 채워 넣는다.

도전과제

아래 행렬의 출력 결과를 생각해보자:

     [,1] [,2]
[1,]    4    1
[2,]    9    5
[3,]   10    7

이 행렬을 작성하는 올바른 명령어는 다음 중 무엇일까? 직접 타이핑하기 전에 각 명령어를 살펴보고, 정답을 생각해보자. 다른 명령어는 어떤 행렬을 만들어낼지도 생각해보자.

  1. matrix(c(4, 1, 9, 5, 10, 7), nrow = 3)
  2. matrix(c(4, 9, 10, 1, 5, 7), ncol = 2, byrow = TRUE)
  3. matrix(c(4, 9, 10, 1, 5, 7), nrow = 2)
  4. matrix(c(4, 1, 9, 5, 10, 7), ncol = 2, byrow = TRUE)

4.7 요약

R 프로그래밍에서 데이터를 다루는 기본적인 자료구조에는 벡터, 요인, 리스트, 데이터프레임 등이 있고 실수형, 정수형, 복소수형, 논리형, 문자형 등의 기본 자료형으로 구성된다.

데이터 타입 간 변환이 필요한 경우 자료형 강제변환(type coercion)이 자동으로 일어나는데, 이는 분석 과정에서 혼란을 야기할 수 있다. 따라서 데이터의 자료형을 잘 이해하고, 필요에 따라 의도적으로 자료형을 변환해주는 것이 무엇보다 중요하다.

데이터프레임은 R에서 가장 널리 사용되는 자료구조로, 내부적으로는 동일한 길이를 갖는 리스트 일종이다. 데이터프레임의 각 칼럼은 벡터로 구성되며, 행은 관측치를 나타낸다.

범주형 데이터를 다룰 때는 요인(factor)이라는 자료구조를 사용한다. 요인은 문자형 데이터처럼 보이지만, 내부적으로는 정수형 코드로 저장되어 효율적인 메모리 관리와 연산을 가능케 한다.

이 외에도 행렬, 리스트 등 다양한 자료구조가 있으며, 데이터의 특성과 분석 목적에 따라 적합한 자료구조를 선택하는 것이 효과적인 데이터 분석의 출발점이 된다.