Devin.KR

기술 통계 - 평균만 보면 틀리는 이유

개발자KR 조회 0

이 장에서 배우는 것

엑셀에서 합계와 평균을 구해 본 사람은 많다. 그런데 평균 하나만 보고 "이 가게는 하루에 이만큼 판다"고 말하면 틀리는 경우가 있다. 이 장에서는 편의점 판매 기록으로 숫자 여러 개를 함께 보면서, 데이터의 중심과 흩어진 정도를 어떻게 읽는지 배운다. 범주별 개수를 세는 방법, 두 값이 함께 움직이는지 재는 방법, 데이터에서 일부를 무작위로 뽑고 그 결과를 다시 똑같이 재현하는 방법도 다룬다.

  • 평균, 중앙값, 절사평균이 같은 데이터에서 왜 다르게 나오는지 설명하고 상황에 맞게 고른다.
  • 분산, 표준편차, 분위수로 흩어진 정도를 재고, 사분위 범위로 이상값 후보를 찾는다.
  • table 과 prop.table 로 범주별 개수와 비율을 구한다.
  • cor 로 상관계수를 구하고, 결측값이 있을 때의 옵션과 해석의 한계를 안다.
  • sample 과 set.seed 로 재현 가능한 표본을 뽑는다.

문제 상황

동네 편의점 사장님이 지난 열흘의 하루 매출을 엑셀에 적어 두었다. 대부분의 날은 4만 6천 원에서 5만 5천 원 사이였다. 그런데 열흘째에는 근처 모임에서 도시락과 음료를 한꺼번에 주문해 34만 9천 원이 찍혔다. 사장님이 열흘 평균을 내 보니 8만 원이었다. 이 숫자를 보고 "하루 평균 8만 원은 판다"고 생각해 알바 인력과 발주량을 잡으면, 열흘 중 아홉 날은 그 절반을 겨우 넘기는 매출에 맞춰 일하게 된다.

또 다른 고민도 있다. 영수증 품목이 수천 건 쌓였는데 전부 들여다볼 수는 없다. 음료가 전체 판매에서 얼마나 차지하는지 알고 싶고, 더운 날에 음료가 더 팔리는지도 확인하고 싶다. 일부만 뽑아서 살펴보려면 어떤 기준으로 뽑아야 하는지, 내일 다시 돌려도 같은 결과가 나오게 하려면 어떻게 해야 하는지도 정해야 한다. 이 장의 도구가 이 질문에 답한다.

중심을 재는 값: 평균과 중앙값

평균은 모든 값의 영향을 받는다

평균(mean)은 모든 값을 더해 개수로 나눈 값이다. 엑셀의 AVERAGE 와 같다. R 에서는 mean() 이다. 평균은 값 하나하나를 모두 반영하기 때문에, 유난히 큰 값 하나가 있으면 그쪽으로 끌려간다.

중앙값(median)은 값을 작은 것부터 세워 놓았을 때 한가운데 오는 값이다. 개수가 짝수이면 가운데 두 값의 평균을 쓴다. 열흘 매출에서는 다섯째 값 50과 여섯째 값 51의 평균인 50.5 가 중앙값이다. 349 가 400 이 되든 4000 이 되든 중앙값은 그대로다. 값의 크기가 아니라 순서만 쓰기 때문이다.

열흘 중 아홉 날은 5만 원 안팎인데 하루의 349 때문에 평균은 80 으로 올라가고 중앙값은 50.5 에 머문다

그림에서 평균선은 거의 모든 막대보다 위에 있다. 평균이 "전형적인 하루"를 가리키지 못하는 전형적인 모양이다. 중앙값선은 막대들의 윗면 근처를 지난다.

절사평균과 비교표

양 끝의 값 일부를 버리고 평균을 내는 방법도 있다. 이것이 절사평균이다. mean(daily, trim = 0.1) 은 작은 쪽 10% 와 큰 쪽 10% 를 버린다. 열 개 중 각 끝에서 하나씩, 곧 46 과 349 를 버리고 나머지 여덟 개로 평균을 낸다. 합이 405 이므로 50.625 다.

같은 열흘 매출에서 통계값마다 349 의 영향을 받는 정도가 다르다
통계값결과(천 원)349 의 영향
평균80.0크다
중앙값50.5없다
10% 절사평균50.6작다
표준편차94.6크다
사분위 범위4.5없다

어느 쪽이 옳다고 고르는 문제가 아니다. 모임 주문도 실제로 들어온 매출이므로, 한 달 총매출을 계산할 때는 평균이 맞는 도구다. 평소의 하루가 어떤지 말할 때는 중앙값이 낫다. 두 값이 크게 다르다는 사실 자체가 "값 몇 개가 튄다"는 신호이므로, 보고할 때는 둘을 나란히 쓰는 습관이 좋다.

흩어진 정도: 분산, 표준편차, 분위수

분산과 표준편차

중심이 같아도 값이 모여 있는지 퍼져 있는지는 다를 수 있다. 분산(variance)은 각 값이 평균에서 얼마나 떨어져 있는지를 제곱해서 모은 값이다. 제곱하는 이유는 평균보다 작은 값의 음수와 큰 값의 양수가 서로 지워지지 않게 하기 위해서다. 열흘 매출에서는 349 가 평균보다 269 큰데, 제곱하면 72,361 이 되어 나머지 아홉 값의 제곱 합 8,109 를 압도한다. 제곱합 80,470 을 9 로 나누면 분산 약 8,941.1 이다.

9 로 나누는 점에 주의한다. R 의 var() 와 sd() 는 개수 10 이 아니라 개수에서 1 을 뺀 값으로 나눈다. 가진 데이터가 더 큰 전체에서 뽑은 일부라고 보고, 일부의 평균을 기준으로 재면 퍼짐이 조금 작게 나오는 경향을 보정하는 방식이다. 엑셀의 VAR.S 와 같다.

분산의 단위는 원본 단위의 제곱이라 읽기가 불편하다. 천 원 단위 데이터의 분산은 "천 원의 제곱"이다. 제곱근을 씌운 표준편차(standard deviation)는 원래 단위로 돌아온다. 94.6 천 원은 "값들이 평균에서 대략 이 정도 떨어져 있다"는 뜻이다. 그런데 열흘 중 아홉 날은 평균 근처에 있지 않다. 표준편차도 평균처럼 튄 값 하나에 끌려가는 통계값이다.

분위수와 사분위 범위

분위수(quantile)는 값을 작은 것부터 줄 세웠을 때 앞에서 몇 퍼센트 지점에 오는 값이다. 50% 지점이 중앙값이고, 25% 와 75% 지점을 1사분위수와 3사분위수라고 부른다. 두 값 사이의 거리 IQR() 이 사분위 범위로, 가운데 절반의 데이터가 펼쳐진 폭이다.

R 의 quantile() 은 기본 설정에서 지점의 위치를 1 + (n - 1) × p 번째로 잡고, 위치가 정수가 아니면 이웃한 두 값을 비례해서 섞는다. 열 개 데이터의 25% 는 3.25 번째이므로 셋째 값 48 과 넷째 값 49 사이에서 4분의 1 만큼 간 지점인 48.25 다. 75% 는 7.75 번째이므로 52 와 53 사이의 52.75 다.

사분위수는 정렬한 순서 위의 위치로 정해지므로 가장 큰 값 349 가 바뀌어도 움직이지 않는다

사분위 범위는 52.75 − 48.25 = 4.5 다. 흔히 쓰는 이상값(outlier) 후보 기준은 3사분위수에 사분위 범위의 1.5 배를 더한 값을 넘는지 보는 것이다. 52.75 + 6.75 = 59.5 를 넘는 값은 349 하나뿐이다. 기준은 관례일 뿐 정답이 아니다. 후보를 찾는 용도로만 쓰고, 지울지 남길지는 그날 무슨 일이 있었는지 확인해서 결정한다. 이 기준은 다음 장에서 그릴 상자 그림이 수염의 길이를 정할 때 쓰는 방식과 같다.

분위수 계산 방법은 여러 가지가 있고 type 인수로 고른다. 기본값은 위에서 설명한 방식이다. 자세한 정의는 quantile 도움말에서 확인할 수 있다.

세기, 관계 보기, 표본 뽑기

table 과 prop.table

숫자가 아니라 "음료", "과자" 같은 범주는 평균을 낼 수 없다. 대신 몇 건인지 센다. table() 은 값마다 개수를 세어 표로 돌려준다. 엑셀의 피벗 표에서 행에 분류를 놓고 개수를 센 결과와 같다. 세는 순서는 factor 의 수준(level) 순서를 따르므로, 원하는 순서가 있으면 factor 를 만들 때 levels 로 지정한다.

prop.table() 은 표의 각 칸을 전체 합으로 나눈다. 개수 8, 5, 4, 3 은 비율 0.40, 0.25, 0.20, 0.15 가 된다. 서로 다른 크기의 데이터를 비교할 때는 개수보다 비율이 낫다. 벡터 두 개를 table() 에 넣으면 교차표가 나오는데, 이때 prop.table(표, 1) 처럼 두 번째 인수를 주면 행 기준 비율이 된다.

개수만 보고 결론을 내리면 금액 구조를 놓친다. 그래서 분류별 금액은 tapply() 로 평균과 중앙값을 함께 구한다. 생활용품은 3건뿐이지만 9,000 원짜리 한 건 때문에 평균이 5,000 원으로 올라가고, 중앙값은 3,500 원이다. 여기서도 두 값의 차이가 튄 값을 알려 준다.

상관계수

상관계수(correlation coefficient)는 두 숫자가 함께 오르내리는 정도를 −1 에서 1 사이의 값으로 나타낸다. 1 에 가까우면 한쪽이 클 때 다른 쪽도 크고, −1 에 가까우면 한쪽이 클 때 다른 쪽이 작으며, 0 근처이면 직선에 가까운 관계가 없다. 기온과 음료 판매량처럼 짝지어진 숫자 두 벡터를 cor() 에 넣는다. 엑셀의 CORREL 과 같다.

기본값은 값 사이의 직선 관계를 재는 방식이다. method = "spearman" 을 주면 값 대신 순위를 써서, 곡선이라도 한 방향으로만 움직이면 1 이 나오고 튄 값의 영향도 줄어든다. 두 벡터 중 한쪽에 NA 가 있으면 결과가 NA 가 된다. use = "complete.obs" 를 주면 둘 다 값이 있는 쌍만 쓴다.

상관이 높다는 사실이 원인을 증명하지는 않는다. 기온이 오르면 음료가 더 팔리는 것으로 보이지만, 같은 데이터만으로는 행사나 요일 같은 제3의 요인을 배제할 수 없다. 상관계수는 "함께 움직인다"까지만 말해 준다.

표본 추출과 set.seed

전체 기록을 모집단(population), 그중에서 뽑은 일부를 표본(sample)이라고 한다. sample(nrow(sales), 5) 는 1 부터 행 수까지의 번호에서 중복 없이 다섯 개를 무작위로 뽑는다. 그 번호로 sales[번호, ] 처럼 행을 고르면 된다. 같은 항목이 다시 뽑힐 수 있게 하려면 replace = TRUE 를 준다.

컴퓨터의 난수는 실제로는 정해진 계산식이 이어 내는 수열이다. 이를 의사난수(pseudo-random number)라고 한다. 수열의 출발점이 시드(seed)이고, set.seed() 로 지정한다. 같은 시드에서 시작하면 같은 순서의 수가 나오므로, 분석 결과를 다른 사람이 같은 값으로 재현할 수 있다. 시드를 지정하지 않으면 실행할 때마다 다른 표본이 뽑힌다. 이 책은 출력이 결정적이어야 하므로 sample 을 부르기 전에 항상 시드를 정한다. 표본의 평균은 전체 평균과 같지 않고 뽑을 때마다 조금씩 다르다는 점도 기억해 둔다. 표본이 클수록 그 차이는 대체로 작아진다.

시드에서 나오는 수의 배열은 R 의 버전과 난수 설정에 따라 달라질 수 있다. 그래서 이 장의 코드는 뽑힌 번호 자체가 아니라 "같은 시드로 두 번 뽑으면 같은가", "개수와 중복 여부는 맞는가"만 출력해 확인한다. 자세한 동작은 sample 도움말에 있다.

tidyverse 대응표

이 책은 외부 패키지 없이 base R 만 쓴다. tidyverse 라는 패키지 모음을 쓰는 코드를 만나면 아래 표로 짝을 찾을 수 있다.

이 장의 base R 함수와 tidyverse 에서 같은 일을 하는 표현
하는 일base Rtidyverse
범주별 개수table(x)count(df, x)
범주별 평균tapply(v, g, mean)group_by(g) 후 summarise(mean(v))
무작위 행 뽑기df[sample(nrow(df), 5), ]slice_sample(df, n = 5)
상관계수cor(x, y)summarise(cor(x, y))

완성 코드

아래 코드를 main.R 한 파일로 저장한다. 데이터는 코드 안에서 만들고 외부 파일과 패키지는 쓰지 않는다.

# main.R - 편의점 판매 기록의 기술 통계

# 1. 데이터: 하루 매출(천 원) 10일치. 마지막 날은 행사 납품이 끼어 있다
daily <- c(52, 48, 50, 47, 55, 49, 51, 53, 46, 349)

# 영수증 품목 20건: 분류와 금액(원)
category <- factor(
  rep(c("음료", "과자", "식품", "생활"), times = c(8, 5, 4, 3)),
  levels = c("음료", "과자", "식품", "생활")
)
amount <- c(1500, 1800, 2000, 1500, 2500, 1800, 1500, 2000,
            1500, 2000, 1800, 3000, 1700,
            3500, 4500, 4000, 6000,
            2500, 9000, 3500)
sales <- data.frame(category = category, amount = amount)

# 2. 중심을 재는 값
cat("== 중심 ==\n")
cat(sprintf("평균: %.1f\n", mean(daily)))
cat(sprintf("중앙값: %.1f\n", median(daily)))
cat(sprintf("10%% 절사평균: %.1f\n", mean(daily, trim = 0.1)))

# 3. 흩어진 정도
cat("== 흩어진 정도 ==\n")
cat(sprintf("범위: %.0f ~ %.0f\n", min(daily), max(daily)))
cat(sprintf("분산: %.1f\n", var(daily)))
cat(sprintf("표준편차: %.1f\n", sd(daily)))
q <- quantile(daily, probs = c(0.25, 0.5, 0.75))
cat(sprintf("사분위수: %.2f, %.2f, %.2f\n", q[1], q[2], q[3]))
upper <- q[3] + 1.5 * IQR(daily)
cat(sprintf("이상값 기준 상한: %.2f\n", upper))
cat(sprintf("상한을 넘는 값: %s\n", paste(daily[daily > upper], collapse = ", ")))

# 4. 개수와 비율
cat("== 개수와 비율 ==\n")
counts <- table(sales$category, dnn = "분류")
print(counts)
print(round(prop.table(counts), 2))
cat(sprintf("음료 비중: %.0f%%\n", 100 * as.numeric(prop.table(counts)["음료"])))

# 5. 분류별 금액
cat("== 금액 ==\n")
cat(sprintf("전체 평균 %.0f원, 중앙값 %.0f원\n", mean(amount), median(amount)))
means <- tapply(sales$amount, sales$category, mean)
meds <- tapply(sales$amount, sales$category, median)
cat(sprintf("%s: 평균 %.0f, 중앙값 %.0f\n", names(means), means, meds), sep = "")

# 6. 관계
cat("== 관계 ==\n")
temp <- c(10, 14, 18, 22, 26, 30)
cups <- c(12, 15, 21, 22, 30, 32)
cat(sprintf("기온-음료 판매량 상관계수: %.2f\n", cor(temp, cups)))
cat(sprintf("순위 상관계수: %.2f\n", cor(temp, cups, method = "spearman")))
cups_na <- c(12, 15, NA, 22, 30, 32)
cat(sprintf("NA 포함: %s\n", cor(temp, cups_na)))
cat(sprintf("결측 쌍 제외: %.2f\n", cor(temp, cups_na, use = "complete.obs")))

# 7. 표본 추출
cat("== 표본 추출 ==\n")
set.seed(2024)
pick1 <- sample(nrow(sales), 5)
set.seed(2024)
pick2 <- sample(nrow(sales), 5)
cat(sprintf("같은 시드의 결과가 같은가: %s\n", identical(pick1, pick2)))
cat(sprintf("표본 크기: %d, 중복 없음: %s\n", length(pick1), !anyDuplicated(pick1)))
cat(sprintf("뽑힌 행 수: %d\n", nrow(sales[pick1, ])))

줄별 해설

  • daily <- c(...): 열흘 매출을 천 원 단위로 담은 숫자 벡터다. 열 번째 값 349 가 행사 납품이 낀 날이다.
  • category <- factor(rep(...), levels = ...): rep 의 times 에 횟수를 벡터로 주면 음료 8번, 과자 5번, 식품 4번, 생활 3번을 이어 붙인다. levels 를 직접 지정해 표에 나오는 순서를 고정한다. 지정하지 않으면 글자 정렬 순서가 되어 환경에 따라 달라질 수 있다.
  • amount 와 data.frame: 분류 순서에 맞춰 금액 20개를 적고 두 열을 하나의 데이터 프레임으로 묶는다. 분류별 금액 개수가 8, 5, 4, 3 이 되도록 줄을 나눠 썼다.
  • sprintf("평균: %.1f\n", ...): %.1f 는 소수 첫째 자리까지 쓰라는 뜻이다. cat 에 여러 값을 넘기면 사이에 공백이 들어가므로, 한 줄을 sprintf 로 완성한 뒤 cat 에 하나만 넘겼다. %% 는 퍼센트 기호 자체를 뜻한다.
  • mean(daily, trim = 0.1): 양 끝에서 10% 씩, 열 개 중 하나씩 버리고 평균을 낸다.
  • var, sd: 제곱합을 개수에서 1 을 뺀 9 로 나눈다. 결과는 8941.1 과 94.6 이다.
  • quantile(daily, probs = ...): 구하려는 지점을 0 과 1 사이의 비율로 준다. 결과는 이름이 붙은 벡터라서 q[1], q[2], q[3] 으로 꺼낸다.
  • upper <- q[3] + 1.5 * IQR(daily): 3사분위수에 사분위 범위의 1.5 배를 더한 이상값 후보 기준이다. daily[daily > upper] 는 기준을 넘는 값만 고르고, paste(..., collapse = ", ") 는 그 값들을 문자열 하나로 잇는다.
  • table(sales$category, dnn = "분류"): 분류별 개수를 센다. dnn 은 표 위에 찍히는 변수 이름이다. prop.table 은 각 칸을 합계 20 으로 나누고, round(..., 2) 로 소수 둘째 자리까지 줄인다.
  • as.numeric(prop.table(counts)["음료"]): 이름으로 음료 칸 하나를 꺼내 보통 숫자로 바꾼 뒤 100 을 곱해 퍼센트로 쓴다.
  • tapply(sales$amount, sales$category, mean): 분류별로 금액을 나눠 함수를 적용한다. 평균과 중앙값을 각각 구하고, sprintf 에 벡터를 그대로 넘겨 네 줄을 한 번에 만든다. 마지막의 sep = "" 는 줄 사이에 공백을 넣지 않도록 한다.
  • cor(temp, cups): 기온과 음료 판매량의 상관계수다. 기온이 오를 때 판매량도 거의 일정하게 오르기 때문에 0.98 이 나온다. method = "spearman" 은 순위의 상관이고, 두 벡터 모두 순서가 완전히 같아서 1.00 이 된다.
  • cups_na: 셋째 날 판매량이 빠진 경우다. 그대로 cor 에 넣으면 NA 가 나오고, use = "complete.obs" 를 주면 나머지 다섯 쌍으로 계산한다.
  • set.seed(2024) 와 sample(nrow(sales), 5): 시드를 두 번 같은 값으로 맞추고 같은 방식으로 뽑아 identical 로 두 결과가 같은지 비교한다. anyDuplicated 는 중복이 있으면 처음 중복된 위치를, 없으면 0 을 돌려주므로 앞에 ! 를 붙이면 "중복 없음"이 된다. sales[pick1, ] 는 뽑힌 번호의 행을 고른다.

실행 결과

터미널에서 main.R 이 있는 폴더로 이동해 다음 명령을 실행한다.

$ Rscript main.R
== 중심 ==
평균: 80.0
중앙값: 50.5
10% 절사평균: 50.6
== 흩어진 정도 ==
범위: 46 ~ 349
분산: 8941.1
표준편차: 94.6
사분위수: 48.25, 50.50, 52.75
이상값 기준 상한: 59.50
상한을 넘는 값: 349
== 개수와 비율 ==
분류
음료 과자 식품 생활 
   8    5    4    3 
분류
음료 과자 식품 생활 
0.40 0.25 0.20 0.15 
음료 비중: 40%
== 금액 ==
전체 평균 2880원, 중앙값 2000원
음료: 평균 1825, 중앙값 1800
과자: 평균 2000, 중앙값 1800
식품: 평균 4500, 중앙값 4250
생활: 평균 5000, 중앙값 3500
== 관계 ==
기온-음료 판매량 상관계수: 0.98
순위 상관계수: 1.00
NA 포함: NA
결측 쌍 제외: 0.99
== 표본 추출 ==
같은 시드의 결과가 같은가: TRUE
표본 크기: 5, 중복 없음: TRUE
뽑힌 행 수: 5

표의 이름 줄과 숫자 줄 끝에는 공백이 하나씩 붙는다. R 이 이름 붙은 값을 찍는 방식이다. 전체 금액 평균 2,880 원과 중앙값 2,000 원의 차이도 생활용품의 9,000 원과 식품의 6,000 원 같은 비싼 품목이 평균을 끌어올린 결과다.

실무에서 자주 틀리는 것

평균 하나만 적어 보고한다

틀린 코드는 평균만 출력하고 "하루 평균 80(천 원)"이라고 기록한다.

daily <- c(52, 48, 50, 47, 55, 49, 51, 53, 46, 349)
cat("하루 평균 매출:", mean(daily), "\n")

평균 80 은 열흘 중 아홉 날보다 훨씬 크다. 중앙값을 곁들이고, 두 값이 크게 다르면 원인이 된 값을 확인한다.

cat(sprintf("평균 %.1f, 중앙값 %.1f\n", mean(daily), median(daily)))
cat("가장 큰 값:", max(daily), "\n")

NA 가 있는데 그대로 통계를 낸다

결측값이 하나라도 들어 있으면 mean, median, sd, quantile 대부분이 NA 를 돌려주거나 오류를 낸다. quantile 은 NA 가 있으면 오류를 낸다.

x <- c(52, 48, NA, 47)
mean(x)
quantile(x)

첫 줄은 NA 를 돌려주고, 둘째 줄은 오류로 멈춘다. na.rm = TRUE 로 빼고 계산하되, 몇 개를 뺐는지도 같이 확인한다. 결측이 많으면 통계값이 전체를 대표하지 못한다.

x <- c(52, 48, NA, 47)
mean(x, na.rm = TRUE)
quantile(x, na.rm = TRUE)
sum(is.na(x))

숫자 하나를 sample 에 넣는다

sample(x, 1) 에서 x 가 길이 1 인 숫자이면, R 은 그 숫자가 아니라 1 부터 그 숫자까지에서 뽑는다. 후보가 하나뿐이거나 후보가 줄어드는 상황에서 조용히 틀린 값이 나온다.

ids <- c(7)
sample(ids, 1)

위 코드는 7 이 아니라 1 에서 7 사이의 수 하나를 돌려준다. 번호가 아니라 위치를 뽑고 그 위치로 꺼내면 후보가 몇 개이든 안전하다.

ids <- c(7)
ids[sample(length(ids), 1)]

set.seed 를 뽑은 뒤에 부르거나 한 번만 부른다

시드는 그 뒤에 나오는 난수부터 영향을 준다. 뽑은 뒤에 시드를 지정하면 그 뽑기는 재현되지 않는다.

pick <- sample(20, 5)
set.seed(2024)

뽑기 직전에 시드를 지정한다. 스크립트에서 뽑기가 여러 번이면 첫 뽑기 앞에 한 번 지정하고, 그 스크립트 전체가 같은 순서로 실행된다는 점을 기억한다. 중간에 뽑기를 하나 추가하면 그 뒤의 결과가 모두 바뀐다. 뽑기마다 결과를 고정하고 싶으면 뽑기마다 시드를 지정한다.

set.seed(2024)
pick <- sample(20, 5)

한눈에 보기

이 장에서 쓴 함수와 읽는 법
함수하는 일튄 값의 영향결측값이 있을 때
mean평균크다na.rm = TRUE
median중앙값작다na.rm = TRUE
var, sd분산, 표준편차(n − 1 로 나눔)크다na.rm = TRUE
quantile, IQR분위수, 사분위 범위작다na.rm = TRUE
table, prop.table개수, 비율해당 없음기본은 NA 를 세지 않음
cor상관계수기본 방식은 크다use = "complete.obs"
set.seed, sample재현 가능한 무작위 추출해당 없음해당 없음

연습 문제

  1. daily 의 마지막 값 349 를 60 으로 바꾼 벡터의 평균과 중앙값을 구하고, 원래 값과 비교하라.
  2. sales 에서 금액이 3,000 원 이상인 품목이 분류별로 몇 건인지 table 로 구하라. 해당 품목이 없는 분류도 0 으로 나와야 한다.
  3. c(2, 4, 6, 8) 의 분산과 표준편차를 손으로 계산하고, var 와 sd 결과와 맞는지 확인하라.
  4. cups 의 부호를 바꾼 -cups 와 기온의 상관계수, 그리고 2 * cups + 5 와 기온의 상관계수를 예상하고 확인하라.

정답과 해설

1번.

daily2 <- c(52, 48, 50, 47, 55, 49, 51, 53, 46, 60)
mean(daily2)
median(daily2)

합은 451 + 60 = 511 이므로 평균은 51.1 이고, 중앙값은 그대로 50.5 다. 튄 값이 사라지자 평균이 80 에서 51.1 로 크게 내려왔는데 중앙값은 움직이지 않았다. 이것이 중앙값이 튄 값에 강하다는 뜻이다.

2번.

table(sales$category[sales$amount >= 3000])

결과는 음료 0, 과자 1, 식품 4, 생활 2 이다. 음료의 최고 금액은 2,500 원이라 해당이 없다. category 가 factor 이고 수준이 네 개 모두 남아 있어서, 걸러낸 뒤에도 음료 칸이 0 으로 표시된다. 문자 벡터였다면 0 건인 분류는 표에서 사라진다.

3번. 평균은 5 이고 편차는 −3, −1, 1, 3 이다. 제곱하면 9, 1, 1, 9 이고 합은 20 이다. 개수 4 에서 1 을 뺀 3 으로 나누면 분산 6.67 이고, 제곱근인 표준편차는 2.58 이다.

v <- c(2, 4, 6, 8)
round(var(v), 2)
round(sd(v), 2)

4 로 나누면 분산 5 가 되는데, 이것은 var 가 내는 값이 아니다.

4번.

round(cor(temp, -cups), 2)
round(cor(temp, 2 * cups + 5), 2)

첫 결과는 −0.98 이고 둘째는 0.98 이다. 부호를 바꾸면 관계의 방향이 반대가 되어 부호만 뒤집힌다. 상수를 곱하고 더하는 것은 단위를 바꾸는 일이어서 상관계수는 변하지 않는다. 그래서 같은 관계를 천 원 단위로 재든 원 단위로 재든 상관계수는 같다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.