openlang

전부 NA인 그룹에 na.rm = TRUE

그룹별 합계를 tapply(x, g, sum, na.rm = TRUE)로 냈더니 한 그룹이 0으로 나왔다. 그 그룹의 값은 전부 NA다. 측정값이 하나도 없는데 표에는 합계가 0이라고 적혀 있다.

x <- c(NA_real_, NA_real_)
sum(x, na.rm = TRUE)
#> [1] 0
mean(x, na.rm = TRUE)
#> [1] NaN
max(x, na.rm = TRUE)
#> [1] -Inf
# plus a warning about no non-missing arguments

하나씩 보면 다 변명할 수 있는 답이다. 빈 합은 0이고 빈 평균은 0/0이다. 그런데 요약표에 나란히 놓으면 아무것도 없었다는 말을 세 가지로 하는 셈이고, 그걸 굳이 알려 주는 건 max뿐이다. 그중 0이 제일 나쁘다. 측정값처럼 보이기 때문이다.

앞으로 내 스크립트에 들어갈 버전은 더할 것이 없으면 NA를 돌려준다.

sum_or_na <- function(x) {
  if (all(is.na(x))) NA_real_ else sum(x, na.rm = TRUE)
}

나는 NA 쪽이 좋다. 적어도 표를 읽는 사람이 그 그룹에 데이터가 없었다는 걸 볼 수 있다.

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    세 결과에 규칙이 하나 있는데 저는 그게 좀 예쁘다고 생각해요. 각각 자기 연산의 항등원을 돌려줘요. 0을 더해도 아무것도 안 바뀌고, -Inf는 무엇보다 작으니 누적 최댓값을 시작하기에 딱 맞는 값이에요. 같은 이유로 아무것도 없을 때 prod는 1을, min은 Inf를 줘요. 평균은 기댈 항등원이 없어서 0/0으로 가고요. R이 대충 하는 게 아니라 대수에 충실한 거예요. 그룹 합계 0이 표에 들어가도 되느냐는 그거랑은 다른 문제고요. 전부 NA인 그 그룹에 prod(x, na.rm = TRUE)를 돌려 보세요. 1도 0만큼 거슬리는지 궁금해요.

  2. 고유 링크

    아, 이거요. 끝까지 tapply만 쓰시나요, 아니면 어딘가에 aggregate도 있나요? aggregate의 공식 메서드는 기본값이 na.action = na.omit이라서 응답값이 NA인 행은 함수가 보기도 전에 빠져요. 전부 NA인 그룹은 거기서 0으로 나오는 게 아니라 결과에서 아예 사라져요. 저도 그룹 수가 하나 줄어서 겨우 알았어요. 행이 이미 없어졌다면 sum_or_na가 NA를 돌려줄 기회나 있을까요?

  3. 고유 링크

    일관적인 건 맞습니다. 다만 그 일관성은 축약 연산의 것이지 표의 것이 아닙니다. 아무것도 없는 합과 실제 값의 합이 둘 다 숫자 하나로 도착하니 호출하는 쪽은 둘을 구별할 수 없습니다. 고치는 모양은 두 가지입니다. NA를 돌려주면 모든 호출자가 빈 경우를 처리하거나 그대로 넘겨야 합니다. 아니면 합계 옆에 그 합에 들어간 값의 개수를 함께 돌려줍니다. 옆에 개수 0이 붙어 있으니 합계 0도 정직해집니다. 요약표에는 첫째가 더 쌉니다. 둘째는 열 하나가 더 들지만, 나중에 누군가 그룹을 데이터 양으로 가중하려 할 때 값을 합니다.

  4. 고유 링크

    알고 보니 둘 다였다. 0을 낸 스크립트는 tapply를 썼다. 같은 폴더의 예전 스크립트는 공식과 함께 aggregate를 썼는데 출력 그룹이 하나 적었고, 나는 그걸 어딘가의 필터 탓으로 넘겼다. 원인은 na.omit이었다. 둘 다 g의 모든 수준을 남기는 tapply(x, g, sum_or_na)로 바꾸자 두 출력이 일치했다. 0이 있던 자리에는 NA가 들어갔고, 사라졌던 그룹도 NA로 돌아왔다. 같은 데이터에서 서로 다른 틀린 답이 두 개 나온 셈이다. 이제부터는 그룹마다 NA 개수를 셋째 열에 적는다.

같은 질문의 다른 갈래

  1. 행이 하나인 행렬의 sum(X)

    관측 행렬을 받아 각 열을 X ./ sum(X)로 정규화하는 함수가 있다. 행은 관측, 열은 특성이다. 오래 문제없이 돌았다. 그러다 행이 하나뿐인 입력이 들어왔다. sum은 크기가 1이 아닌 첫 번째 차원을 따라 더한다.…

    답글 6matlab
  2. 열마다 새로 할당하던 루프

    Julia에서 슬라이싱은 복사를 해요. 알고 있었는데 루프 안에서는 깜빡했어요. 행렬의 열을 돌면서 j마다 s += sum(A[:, j])를 하는 함수를 예로 들어 볼게요. A[:, j]가 매번 새 벡터를 만들고, 더하고,…

    답글 4julia
  3. f2py가 C 순서 배열을 호출마다 복사한다

    f2py로 NumPy 배열을 Fortran 서브루틴에 넘긴다. 배열은 평소처럼 np.zeros((n, m))로 만들었으니 C 순서다. Fortran은 열이 연속으로 놓여 있길 원한다. f2py도 그걸 알고, 일반 입력 인자라…

    답글 3fortran