ADsP NOTE검색

데이터 분석 / 7분 읽기

R 자료구조 읽기, 벡터와 데이터 프레임의 결과가 다른 이유

R의 원자 벡터는 원소의 자료형을 맞추지만 리스트는 서로 다른 형과 길이를 보관하고, 데이터 프레임은 열마다 다른 형을 유지합니다. 같은 값이 보여도 자료구조가 다를 수 있으므로 형, 선택 위치, 결측값 순서로 확인하면 코드 결과를 판단하기 쉽습니다.

합계를 구하기 전에 값의 종류부터 확인합니다

학습 시간을 20분, 35분, 미기록으로 입력했는데 평균이 나오지 않는다면 어떤 문제일까요? 미기록을 문자로 넣었는지, 결측값으로 넣었는지에 따라 원인이 달라집니다. R에서는 눈에 보이는 숫자뿐 아니라 값을 담은 자료구조와 자료형을 함께 읽어야 합니다.

함수 이름 뒤 괄호 안에는 사용할 값이나 설정을 넣습니다. 왼쪽 화살표 모양의 대입 연산자 <-는 오른쪽 결과를 왼쪽 이름에 저장합니다. 예제는 추가 패키지가 필요 없는 기본 R 문법과 가상 학습 자료를 사용합니다.

벡터는 원소의 형을 맞추고 리스트는 각각 보관합니다

벡터는 값을 한 줄로 담는 구조입니다. 여기서 말하는 원자 벡터의 원소는 하나의 자료형을 공유합니다. c는 값을 이어 붙입니다. 숫자와 문자를 함께 넣으면 문자형 벡터로 맞춰질 수 있습니다. 이때 숫자로 보이는 문자도 덧셈에 바로 쓸 수는 없습니다.

minutes <- c(20, 35, 25)
mixed <- c(20, "미기록", 25)
record <- list(minutes = minutes, note = "셋째 날 복습")
typeof(minutes)
typeof(mixed)
record[[1]][2]

위 결과를 순서대로 예상하면 double, character, 35입니다. 첫 벡터는 수치형이고 두 번째는 문자형입니다. 리스트는 원소마다 다른 형과 길이를 담을 수 있으므로 학습 시간 벡터와 메모 문장을 각각 보관합니다. 리스트의 첫 원소를 꺼낸 뒤 그 벡터의 두 번째 값을 골랐기 때문에 마지막 결과는 35입니다.

리스트에서 대괄호 하나를 쓰는 record[1]은 첫 원소를 담은 작은 리스트입니다. 겹대괄호를 쓰는 record[[1]]은 그 원소의 내용물인 시간 벡터입니다. 같은 20, 35, 25가 화면에 보여도 반환된 구조는 다릅니다. 출력의 겉모양이 같아도 같은 객체는 아닐 수 있습니다.

행렬과 데이터 프레임은 열을 다르게 취급합니다

행렬은 행과 열을 갖춘 2차원 구조입니다. 입문 단계에서 다루는 일반적인 원자 행렬은 전체 원소가 같은 자료형입니다. 데이터 프레임도 표처럼 보이지만 열마다 다른 형을 담을 수 있습니다. 한 열은 이름, 다른 열은 시간, 또 다른 열은 복습 여부여도 됩니다. 각 열은 같은 관측 행에 대응해야 합니다.

구조먼저 확인할 기준이 예제에서 알맞은 내용
원자 벡터원소가 한 자료형을 공유하는가날짜별 학습 시간
리스트원소마다 형과 길이가 달라도 되는가시간 벡터와 메모 묶음
행렬행과 열이 있으며 전체 형을 맞추는가날짜별 두 과목의 점수
데이터 프레임열마다 형을 유지하는가학습자 이름과 시간 표
팩터정해진 범주 수준으로 표현하는가입문, 복습 등 학습 단계
scores <- matrix(c(10, 20, 30, 40), nrow = 2)
scores[1, 2]
study <- data.frame(name = c("가", "나"), minutes = c(20, 35))
study[, "minutes"]
study[, "minutes", drop = FALSE]

matrix는 기본적으로 열부터 채웁니다. 첫 열은 10, 20이고 둘째 열은 30, 40이어서 첫 행 둘째 열은 30입니다. 행부터 채우려면 byrow = TRUE를 명시합니다. 입력한 값이 행 순서로 채워질 것이라고 생각하면 결과를 잘못 읽기 쉽습니다.

데이터 프레임에서 열 하나를 고르면 기본 설정에 따라 벡터로 단순화될 수 있습니다. 위에서 첫 선택은 20, 35인 벡터이고, drop = FALSE를 붙인 선택은 한 열짜리 데이터 프레임입니다. 다음 함수에 표를 넘겨야 한다면 값뿐 아니라 모양도 유지합니다.

인덱스는 위치와 조건 중 무엇을 가리키는지 읽습니다

R의 위치 인덱스는 1부터 시작합니다. 양수 인덱스는 해당 위치를 고르고 음수 인덱스는 해당 위치를 제외합니다. 논리형 인덱스는 TRUE 위치를 고릅니다. 서로 다른 방법을 무심코 섞으면 원하는 대상을 고르기 어렵습니다.

x <- c(20, 35, 25)
x[c(1, 3)]
x[-2]
x[x >= 25]

첫 결과와 둘째 결과는 모두 20, 25입니다. 하나는 첫째와 셋째를 선택했고 다른 하나는 둘째를 제외했습니다. 셋째 결과는 35, 25입니다. 조건이 각 값과 비교되어 FALSE, TRUE, TRUE가 된 뒤 해당 위치를 고르기 때문입니다. 결과가 우연히 같아도 값을 고른 방식은 서로 다릅니다.

벡터 연산에서는 짧은 벡터가 반복되는 재활용 규칙도 만납니다. c(10, 20, 30, 40) + c(1, 2)는 11, 22, 31, 42입니다. 두 번째 벡터가 1, 2, 1, 2로 맞춰집니다. 길이가 정확히 나누어떨어지지 않으면 경고가 날 수 있으므로, 경고가 없다는 사실만으로 의도한 계산이라고 결론 내리지는 않습니다.

결측값은 0과 다르게 처리합니다

NA는 값이 알려지지 않았음을 나타냅니다. 학습 시간이 0분인 날과 기록하지 않은 날은 구분해야 합니다. 미기록을 0으로 바꾸면 평균의 의미가 달라집니다.

minutes <- c(20, NA, 40)
is.na(minutes)
mean(minutes)
mean(minutes, na.rm = TRUE)
sum(!is.na(minutes))

결측 위치를 묻는 is.na의 결과는 FALSE, TRUE, FALSE입니다. 기본 평균은 NA이고 결측을 제외한 평균은 30입니다. 실제 평균에 사용된 관측값은 2개입니다. na.rm = TRUE는 결측을 해결하거나 복구하는 명령이 아니라 이번 계산에서 제외하는 설정입니다. 제외된 날의 시간이 유난히 길거나 짧았다면 관측된 두 날만의 평균이 전체 기간을 대표하지 못할 수 있습니다.

minutes == NA로 결측을 찾으려 하면 각 비교가 알려지지 않은 값과의 비교가 되어 NA가 나옵니다. 결측 여부는 is.na로 확인하세요. 조건을 이용해 추출할 때도 결측이 남아 있는지 먼저 살피면 원인 모를 NA 행을 줄일 수 있습니다.

팩터는 범주 이름과 내부 번호를 구분합니다

팩터는 가능한 범주 수준을 정해 값을 표현합니다. 숫자처럼 보이는 범주 이름을 팩터로 만들었다고 해서 그 숫자 자체가 내부에 저장되는 것은 아닙니다.

f <- factor(c("20", "10", "20"), levels = c("10", "20"))
as.numeric(f)
as.numeric(as.character(f))

첫 변환은 2, 1, 2입니다. 수준 목록에서 10은 첫째, 20은 둘째이기 때문입니다. 두 번째 변환은 범주 이름을 문자로 꺼낸 뒤 숫자로 읽어서 20, 10, 20을 얻습니다. 범주 이름이 입문과 복습처럼 숫자가 아니라면 이 두 번째 방법도 수치 변환의 해답은 아닙니다.

순서가 있는 범주에는 ordered = TRUE를 사용할 수 있지만, 순서가 있다고 범주 사이 간격이 같아지는 것은 아닙니다. 입문과 중급의 차이가 중급과 고급의 차이와 같은 수량인지 판단하는 문제는 자료형 설정과 별개입니다.

숫자로 저장됐다는 이유만으로 덧셈이나 평균이 의미 있는 것은 아닙니다. 팩터의 범주와 수량이 어떻게 다른지는 측정 척도 구분에서도 확인할 수 있습니다.

핵심 정리와 실수 점검

핵심 정리

  • 원자 벡터와 일반적인 원자 행렬은 원소의 자료형을 맞추고 데이터 프레임은 열마다 다른 형을 유지합니다.
  • 리스트의 [ ]는 부분 리스트를, [[ ]]는 원소의 내용물을 선택합니다.
  • 팩터의 내부 번호와 범주 이름을 구분하고 NA는 is.na로 확인합니다.

놓치기 쉬운 지점

  • 문자형 숫자를 수치형이라고 보고 바로 평균을 구하는 실수
  • na.rm = TRUE를 결측값 복구로 해석하는 실수. 계산에서 제외한 관측 수도 함께 확인합니다.

확인할 내용

  • 벡터, 리스트, 행렬, 데이터 프레임을 담을 값에 맞게 구분한다.
  • matrix의 기본 채우기 방향과 행, 열 인덱스를 설명한다.
  • 결측을 제외한 평균의 분모가 몇 개인지 적는다.
  • 팩터를 숫자로 변환했을 때 범주 이름인지 내부 번호인지 확인한다.

이해한 내용 확인하기

직접 작성한 학습용 문항입니다. 공식 기출문제가 아닙니다.

Q1. c(5, "8", 10)의 자료형은 무엇이며 바로 합계를 구할 수 있나요?

생각한 뒤 해설 보기

문자형 벡터입니다. 문자 "8"에 맞춰 다른 원소도 문자로 변환되므로 그대로 sum에 넣어 수치 합계를 구할 수 없습니다. 모두 숫자로 해석 가능한지 확인한 뒤 명시적으로 변환해야 합니다.

Q2. matrix(1:6, nrow = 2)에서 둘째 행 셋째 열은 무엇인가요?

생각한 뒤 해설 보기

6입니다. 열별로 1, 2 / 3, 4 / 5, 6이 채워집니다. byrow = TRUE이면 행별로 1, 2, 3 / 4, 5, 6이 채워지므로 첫 행 둘째 열처럼 달라지는 위치도 함께 점검하세요.

Q3. list(a = c(4, 9), b = "복습")에서 첫 원소의 내용물인 벡터를 고르는 표현은 무엇인가요?

생각한 뒤 해설 보기

[[1]] 또는 $a입니다. [1]은 첫 원소를 담은 부분 리스트이며 내용물 벡터와 자료구조가 다릅니다.

Q4. c(0, NA, 30)의 결측 제외 평균은 10인가요, 15인가요?

생각한 뒤 해설 보기

15입니다. 알려진 0과 30 두 값을 사용합니다. NA를 0으로 대체했을 때 나오는 10은 별도의 가정을 넣은 계산입니다.

Q5. factor(c("30", "10"), levels = c("30", "10"))을 as.numeric으로 바꾸면 30, 10이 되나요?

생각한 뒤 해설 보기

아닙니다. 수준 순서에 따른 내부 번호 1, 2가 됩니다. 숫자처럼 보이는 범주 이름과 내부 번호를 구분해야 합니다.

객관식 유형 연습 →

더 궁금한 점

R을 설치하지 않고 읽어도 되나요?

짧은 코드는 손으로 결과를 추적할 수 있습니다. 다만 객체의 구조와 경고 메시지는 R에서 직접 확인하는 편이 좋습니다. 이 글의 코드는 기본 R 함수만 사용하며 특정 개발 도구는 요구하지 않습니다.

참고 자료와 확인 범위
  • R 공식 입문서 ↗

    벡터, 인덱스, 결측값, 행렬, 리스트 및 데이터 프레임의 기본 규칙을 확인했습니다. 학습 시간 예제와 문제는 자체 제작했습니다.

  • R 공식 factor 문서 ↗

    수준과 내부 정수 코드, 숫자 변환 시 주의사항을 확인했습니다.

함께 다듬는 노트

이해되지 않는 설명이 있었나요?

틀린 계산, 어려운 표현, 다음에 읽고 싶은 주제를 알려주세요.

의견 보내기