리스트는 무엇이든 담습니다
벡터에는 엄격한 규칙이 하나 있습니다. 모든 원소가 같은 타입이어야 한다는 것이죠. 리스트에는 그런 규칙이 없습니다. 리스트의 각 원소는 서로 다른 타입, 다른 길이, 심지어 또 다른 리스트일 수도 있습니다. R의 범용 컨테이너입니다.
length()는 4를 보고합니다. 그중 하나(scores)가 숫자 세 개를 담고 있어도 원소는 네 개입니다. 리스트는 칸 안의 내용물이 아니라 칸의 개수를 셉니다. str()(structure)은 리스트가 실제로 무엇을 담고 있는지 확인하는 가장 빠른 방법으로, 원소마다 이름·타입·미리보기를 한 줄씩 보여 줍니다. str()을 반사적으로 쓰세요. 어떤 함수가 무언가를 돌려주었는데 그것이 무엇인지 확실하지 않을 때마다 str()을 걸어 보세요.
이름은 선택 사항이지만(list(1, "a", TRUE)도 유효합니다) 거의 항상 붙이는 편이 낫습니다. 이름 있는 리스트는 스스로를 설명합니다.
들어가는 세 가지 방법: [, [[, $
리스트에서 모두가 한 번은 틀리는 부분이니 정확히 짚고 넘어갑시다. 접근 연산자는 세 개인데 하는 일은 두 가지입니다.
[는 더 작은 리스트를 돌려줍니다. 선택한 것을 담은 같은 종류의 컨테이너입니다.[[는 원소 자체, 즉 칸 안의 실제 값을 돌려줍니다.$는 이름을 문자 그대로 쓰는[[의 축약형입니다.person$age는person[["age"]]입니다.
무심코 출력할 때는 차이가 보이지 않다가, 계산하려는 순간 아주 뚜렷해집니다.
person["scores"]의 클래스는 "list"입니다. 점수 벡터를 안에 담은 길이 1의 리스트죠. person[["scores"]]의 클래스는 "numeric"으로, mean()에 바로 넣을 수 있는 벡터 자체입니다. 대괄호 하나 버전에 mean()을 호출하면 인자가 수치형이 아니라는 오류가 납니다. 그 오류 메시지는 거의 항상 [[를 의도한 곳에 [를 썼다는 신호입니다.
기억에 남는 비유가 있습니다. 리스트는 기차입니다. [는 더 짧은 기차를 줍니다. 여전히 기차죠. [[는 객차를 열어 화물을 건네줍니다.
그렇다면 [가 알맞은 도구인 때는 언제일까요? 여러 원소를 원하면서 포장된 채로 유지하고 싶을 때입니다.
두 원소를 "그 자체로" 한 번에 꺼낼 수는 없습니다. 값 두 개는 컨테이너가 필요하니까요. 그래서 다중 원소 선택은 언제나 대괄호 하나이고 언제나 리스트를 돌려줍니다.
차이가 하나 더 있습니다. $는 부분 매칭을 사용합니다(person$sc는 모호하지 않다면 scores를 찾아냅니다). 콘솔에서는 편리하지만 스크립트에서는 부담입니다. 계속 동작해야 하는 코드에서는 전체 이름을 쓰는 [[를 택하세요. [[는 변수에 담긴 이름도 받아들이지만 $는 그럴 수 없습니다.
추가, 변경, 제거
리스트는 평범한 대입으로 늘어나고 줄어듭니다. 특별한 append 메서드가 필요 없습니다.
- 아직 없는 이름에 대입하면 원소가 추가됩니다.
- 이미 있는 이름에 대입하면 교체됩니다.
length(x) + 1위치에 대입하면 이름 없는 원소가 덧붙습니다.NULL을 대입하면 원소가 완전히 제거되어 리스트가 짧아집니다. (NULL이 삭제 명령으로 동작하는 유일한 곳입니다. 칸에 진짜로 "아무것도 없음"을 저장해야 한다면x["k"] <- list(NULL)을 쓰세요.)
두 리스트를 끝에서 끝으로 이어 붙이려면 벡터에서와 마찬가지로 c()가 동작합니다. c(list_a, list_b)는 하나의 더 긴 리스트를 돌려줍니다.
중첩 리스트
리스트의 원소가 또 다른 리스트일 수 있기 때문에 리스트는 원하는 깊이까지 중첩됩니다. 그래서 파싱된 JSON이나 그룹별 결과처럼 구조화된 데이터에 R에서 자연스러운 형태입니다.
파고드는 것은 접근자를 연결하는 일일 뿐입니다. $나 [[ 하나가 한 단계씩 더 깊이 들어갑니다. 중첩 구조가 헷갈릴 때 str(company)는 전체 트리를 한 번에 보여 주고, str(company, max.level = 1)은 맨 위 층만 보여 줍니다.
unlist()로 평탄화하기
unlist()는 아무리 깊이 중첩된 리스트라도 하나의 벡터로 접어 넣습니다.
두 가지를 눈여겨보세요. 첫째, unlist()는 이름을 만들어 주므로(math1, math2, art) 각 값이 어디서 왔는지 여전히 알 수 있습니다. 둘째, 이것이 함정인데, 벡터는 한 가지 타입만 담으므로 섞인 리스트를 unlist 하면 모든 것이 강제 변환됩니다. 숫자 1이 문자열 "1"로 돌아왔습니다. unlist 했더니 숫자가 텍스트가 되었다면, 그 리스트는 생각만큼 균일하지 않았던 것입니다.
리스트가 중요한 이유
리스트는 곧 벗어나게 될 입문용 주제처럼 느껴질 수 있습니다. 사실은 정반대로, 언어 전체를 떠받치고 있습니다.
- 여러 가지를 돌려주는 함수는 리스트를 돌려줍니다. R 함수는 객체 하나를 반환하는데, 리스트 덕분에 그 하나가 여기엔 적합값을, 저기엔 계수 표를 담을 수 있습니다. 회귀를 실행하면 돌려받는 모델 객체는 (크고 클래스가 붙은) 리스트입니다.
str()을 걸어 보면 확인됩니다. - 데이터 프레임은 열들의 리스트입니다. 길이가 같은 벡터들 위에 동작이 조금 얹힌 것이죠. 방금 배운 모든 것(
$,[[, 원소를 지우는NULL대입)이 데이터 프레임 열에 그대로 적용됩니다. - apply 계열 -
lapply()와 그 친구들 - 은 리스트를 받아 각 원소에 함수를 적용하고 리스트를 돌려줍니다. 리스트가 들어가고 리스트가 나오는 것이 R에서 반복 계산의 표준 형태입니다.
핵심 정리
- 리스트는 무엇이든 담습니다. 섞인 타입, 서로 다른 길이, 다른 리스트, 데이터 프레임 전체까지 담습니다.
[는 더 작은 리스트를,[[는 원소 자체를 돌려주고$는 이름을 문자 그대로 쓰는[[입니다. 계산하려 한다면[[나$가 필요합니다.- 새 이름에 대입해 추가하고,
NULL을 대입해 제거하며,c()로 결합합니다. unlist()는 벡터로 평탄화하면서 섞인 타입을 강제 변환합니다. 결과의 클래스를 확인하세요.- 데이터 프레임은 열들의 리스트이므로 이 지식이 그대로 전이됩니다.
다음 순서는 행렬입니다. 전부 한 타입인, 행과 열로 이루어진 리스트의 대응물입니다.
자주 묻는 질문
R에서 [와 [[의 차이는 무엇인가요?
대괄호 하나 [는 선택한 원소들을 담은 리스트, 즉 같은 종류의 더 작은 컨테이너를 항상 돌려줍니다. 대괄호 둘 [[는 안으로 들어가 원소 자체를 돌려줍니다. x$scores가 수치 벡터를 담고 있다면 x["scores"]는 길이 1의 리스트이고 x[["scores"]]는 그 수치 벡터입니다. 값을 실제로 다루고 싶다면 [[(또는 $)를 쓰세요.
R에서 리스트에 원소를 어떻게 추가하나요?
아직 없는 이름에 대입하세요. x$email <- "rosa@example.com"이나 x[["email"]] <- ...처럼 하면 리스트가 자동으로 늘어납니다. 이름 없이 덧붙이려면 다음 위치에 대입하세요. x[[length(x) + 1]] <- value입니다. 원소를 제거하려면 NULL을 대입하세요. x$email <- NULL입니다.
R에서 리스트를 벡터로 어떻게 변환하나요?
unlist(x)는 (중첩된 것을 포함해) 리스트를 하나의 벡터로 평탄화합니다. 벡터는 한 가지 타입만 담으므로 모든 것이 존재하는 것 중 가장 유연한 타입으로 강제 변환됩니다. 숫자와 문자열이 섞인 리스트는 전부 문자인 벡터가 됩니다. unlist 후에는 결과의 class()를 확인하세요.