Menu

R의 apply, lapply, sapply: apply 계열 완전 정리

apply, lapply, sapply, vapply, mapply, tapply로 이루어진 apply 계열은 데이터의 모든 원소에 함수를 실행합니다. 각각이 무엇을 하고 언제 써야 하는지 살펴봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

한 문장으로 보는 apply 계열

apply 계열의 모든 구성원은 같은 일을 합니다. 함수를 받아 어떤 구조의 모든 원소에 실행하고 결과를 모으는 것이죠. 달라지는 것은 입력의 모양과 출력의 모양입니다. 지도는 이렇습니다.

  • apply(m, MARGIN, FUN) - 행렬의 행 또는 열.
  • lapply(x, FUN) - 리스트나 벡터의 각 원소. 항상 리스트를 돌려줍니다.
  • sapply(x, FUN) - lapply와 같지만 가능하면 벡터나 행렬로 단순화합니다.
  • vapply(x, FUN, FUN.VALUE) - 반환 타입을 선언하고 강제하는 sapply.
  • mapply(FUN, x, y, ...) - 여러 입력을 원소 단위로 나란히 훑습니다.
  • tapply(values, groups, FUN) - 각 그룹 안에서 FUN을 적용해 그룹마다 결과 하나를 줍니다.

이들 모두 여러분이 직접 쓴 함수를, 익명 함수까지 포함해 받아들입니다. 이 계열의 힘이 나오는 지점입니다.

apply(): 행렬의 행과 열

apply는 행렬, MARGIN(행은 1, 열은 2), 그리고 함수를 받습니다.

행렬은 열 방향으로 채워지므로 행은 1 3 52 4 6입니다. 행 합계는 9 12로, 열 합계는 3 7 11로 출력됩니다. 가장 흔한 두 경우에는 기본 R이 전용의 더 빠른 도우미(rowSums, colSums, rowMeans, colMeans)를 제공하므로, applyapply(m, 2, max)처럼 전용 함수가 없는 경우에 쓰세요.

함정이 하나 있습니다. 데이터 프레임에 apply를 쓰면 먼저 조용히 행렬로 변환되고, 그 과정에서 모든 열이 하나의 공통 타입으로 강제 변환됩니다. 데이터 프레임에서는 열을 리스트로 취급해 lapply/sapply를 쓰세요.

lapply()는 항상 리스트를, sapply()는 단순화한 결과를 줍니다

lapply는 각 원소에 함수를 대응시키고 무슨 일이 있어도 같은 길이의 리스트를 돌려줍니다.

같은 계산인데 모양이 둘입니다. lapply8580을 담은 리스트를 돌려주고, sapply는 모든 결과의 길이가 1임을 알아채고 이름 있는 수치 벡터로 단순화합니다. 읽기에도 좋고 이어지는 계산에 넣기에도 훨씬 편합니다.

다만 이 편리함에는 날이 서 있습니다. sapply의 반환 타입은 데이터에 의존합니다. 원소 하나라도 길이가 다르면 단순화가 실패해 조용히 다시 리스트가 나옵니다.

대화형에서는 어깨를 으쓱하고 넘어갈 일이지만, 스크립트 안에서는 일 년 내내 잘 돌던 코드가 데이터 모양이 바뀌는 날 깨진다는 뜻입니다. vapply가 존재하는 이유가 바로 그것입니다.

vapply(): 타입 안전한 sapply

vapply는 세 번째 인자 FUN.VALUE를 추가합니다. 결과 하나가 어떤 모습이어야 하는지 선언하는 틀이죠. integer(1)은 "각 호출이 정확히 정수 하나를 돌려준다"는 뜻입니다.

이름 있는 정수 벡터 5 6 6을 얻고, 더 중요하게는 보장을 얻습니다. nchar가 언젠가 값 두 개나 문자를 돌려준다면 vapply는 모양이 어긋난 결과가 아래로 흘러가게 두지 않고 호출 지점에서 오류로 멈춥니다. 이 선언은 문서 역할도 겸합니다.

vapply(words, nchar, character(1))
# Error in vapply(words, nchar, character(1)) : values must be type 'character'

경험 법칙은 이렇습니다. 콘솔에서는 sapply, 사람이 지켜보지 않아도 돌아가야 하는 함수와 스크립트에서는 vapply입니다.

mapply()와 tapply(): 병렬 입력과 그룹

lapply는 구조 하나를 훑습니다. 각 호출이 여러 구조의 같은 위치에서 원소를 필요로 한다면 mapply를 쓰세요. 함수가 맨 앞에 온다는 점에 주의하세요.

출력의 각 원소는 첫 값끼리, 그다음 둘째 값끼리 짝지어집니다. 10 200 3000 40000입니다.

tapply는 그룹 담당입니다. valuesgroups로 나눈 뒤 각 그룹 안에서 함수를 적용해 그룹마다 결과 하나를 돌려줍니다.

그룹 a의 평균은 30, 그룹 b의 평균은 40입니다. "범주별 평균"에 대한 기본 R의 답이며, 데이터 프레임에 대해 group_by + summarize가 답하는 것과 같은 형태의 질문입니다. 이미 dplyr 영역에 있다면 그쪽을 쓰고, 벡터 두 개만 가지고 한 줄로 끝내고 싶을 때 tapply가 빛납니다.

계열 전체에 걸쳐 통하는 편의 기능이 두 가지 있습니다. 함수 뒤에 오는 추가 인자는 매 호출마다 그 함수로 전달됩니다.

그리고 기성 함수가 맞지 않을 때는 익명 함수가 끼어듭니다. sapply(x, \(v) max(v) - min(v))는 도우미 함수에 이름을 붙이지 않고도 원소별 범위를 계산합니다.

apply 계열과 for 반복문

R에서 for 반복문이 느리고 apply 계열이 빠르다는 말을 듣게 됩니다. 대부분 속설입니다. 정말로 느린 것은 반복문 안에서 결과를 키우는 것입니다. result <- c(result, new_value)는 매 회차마다 벡터 전체를 복사합니다. 출력을 미리 할당하는 반복문은 성능이 괜찮습니다.

그러니 성능이 아니라 가독성으로 고르세요. apply 버전은 한 줄로 무엇을 하는지("각 원소를 제곱하라") 말하고 할당도 대신 처리해 줍니다. 단순한 원소 단위 작업에서 그것이 관용구인 이유죠. for 반복문은 회차가 이전 결과에 의존할 때, break로 일찍 빠져나와야 할 때, 또는 본문이 길어서 람다가 도움보다 방해가 될 때 제 값을 합니다. 둘 다 정당한 R입니다.

핵심 정리

  • 계열 전체가 하나의 아이디어입니다. 모든 원소에 함수를 실행하고 결과를 모으는 것이죠.
  • apply는 행렬의 행(MARGIN = 1)과 열(MARGIN = 2)을 위한 것입니다. 전용 함수가 있다면 rowSums/colMeans를 택하세요.
  • lapply는 항상 리스트를 돌려주고, sapply는 가능하면 단순화합니다. 즉, 반환 타입이 데이터에 따라 바뀔 수 있습니다.
  • vapply는 반환 타입을 고정합니다. 놀라움이 있어서는 안 되는 코드에 쓰세요.
  • mapply는 여러 입력을 지퍼처럼 묶고, tapply는 그룹 안에서 값을 집계합니다.
  • for 반복문이 본질적으로 느린 것이 아니라 벡터를 키우는 것이 느립니다. 가장 잘 읽히는 표기를 고르세요.

다음 순서는 파이프입니다. 이런 호출들을 읽기 좋은 단계별 파이프라인으로 이어 주는 연산자입니다.

자주 묻는 질문

R에서 lapply와 sapply의 차이는 무엇인가요?

lapply는 예외 없이 항상 리스트를 돌려줍니다. sapply는 같은 계산을 한 뒤 결과를 단순화하려고 시도합니다. 모든 원소의 길이가 1이면 벡터로, 길이가 같으면 행렬로, 단순화할 수 없으면 다시 리스트로 돌려줍니다. 대화형에서는 sapply가 편하고, 스크립트에서는 반환 타입이 결코 바뀌지 않는 lapply(또는 vapply)가 더 안전합니다.

R에서 apply()는 무슨 일을 하나요?

apply(m, MARGIN, FUN)은 행렬에 FUN을 실행합니다. MARGIN = 1은 각 행에, MARGIN = 2는 각 열에 적용합니다. apply(m, 1, sum)은 행 합계를, apply(m, 2, mean)은 열 평균을 줍니다. 행렬과 배열을 위한 것이며, 리스트와 벡터에는 lapply/sapply를 쓰세요.

R에서 apply 계열이 for 반복문보다 빠른가요?

대개 그렇게 크게 빠르지 않습니다. 그것은 속설입니다. 결과 벡터를 미리 할당한 잘 쓴 for 반복문은 비슷한 성능을 냅니다. for를 나쁘게 만든 반복문들은 결과를 한 원소씩 키우며 매 회차마다 전부 복사한 것들입니다. apply 계열은 속도가 아니라 간결함과 의도 표현을 위해 선택하세요.

R에서 vapply는 어디에 쓰나요?

vapply는 계약이 붙은 sapply입니다. 각 결과의 타입과 길이를 선언합니다. 예: vapply(x, nchar, integer(1)). 함수가 그 밖의 것을 돌려주면 R이 예상 밖의 구조를 조용히 건네는 대신 즉시 오류를 냅니다. 사람이 지켜보지 않아도 계속 동작해야 하는 코드에서는 이쪽을 택하세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기