태그 보관물: 통계

R로 스타벅스 지역별 매장수 분석, 데이터 크롤, 데이터 분석

R언어로 , 스타벅스 매장 데이터를 크롤해서 분석하는 간단한 예제 스크립트입니다.

전체 코드는 글 아래 쪽에 있습니다.

코드 설명

예제에서 하려는 것

간단하게 재미 삼아하는 토이(toy) 분석입니다. 실제 분석 프로젝트에서는 이 보다는 더 심도가 깊게 해야 합니다.

이 분석의 실제 목적은 R로 데이터 크롤을 해서 분석은 어떻게 하는 것인지 설명하는 것입니다. 분석 리포트에서는 크롤한 데이터를 이용해서 인사이트를 도출하고 탐색적데이터분석(EDA)를 어떻게 하는지 예제로 보여줍니다.

이 분석에서 해보려고 하는 것은 스타벅스의 매장 목록을 가져와서 각 시도별, 구군별로 어느 지역에 스타벅스 매장이 가장 많은지 어떤 인사이트가 있는지 살펴보고 또 지역별 인구수와 스타벅스 매장 수와 관계가 있는지를 확인해 보는 간단한 분석입니다.

분석에 들어가기 전에 상식적으로 식음료 판매 매장들은 유동인구가 많은 곳에 자리를 잡는 것이 일반적이기 때문에 당연히 지역별 인구수와 스타벅스 매장 수는 관련이 있을 것이라고 추측할 수 있습니다.

그래서 서울이라면 강남 지역에 스타벅스가 가장 많을 것이라고 추측해 볼 수 있습니다. 서울은 강남에 오피스가 가장 많아서 직장이들도 많고 유동 인구도 많습니다. 정말 그런지 확인은 해봐야 겠지요.

그래서 여기서 확인해 볼 가설은 “스타벅스 매장의 위치는 지역의 인구수 또는 유동인구와 관련이 있을 것이다.” 라는 것입니다.

너무 뻔한 것이어서 굳이 세심하게 분석할 필요가 없다고 생각할지 모르지만 하지만 이미 알고 있거나 너무 뻔한 사실도 실제로 그런지 확인하는 것도 유의미합니다.

의외성이 많아지는 요즘 세상은 알고 있던 상식이 실제와는 다른 경우도 많기 때문입니다.

먼저 알아야 할 것

이 포스트에서 하려는 것을 할 때 꼭 알아야 할 정보와 필요한 것은 3가지입니다.

  • 매장의 위치 데이터 제공처
  • 매장의 위치데이터 데이터를 크롤(스크랩)하는 법
  • 크롤한 데이터를 잘 정제, 정돈하는 법

R언어 사용법 등은 당연히 알아야 하는 것이라 목록에는 안 적었습니다.

Python이나 다른 언어로 해도 되지만 여기서는 R언어로 하겠습니다.

데이터를 얻어 오는 곳

이런 정보가 어디 있는지는 검색을 해서 알아내야 합니다. 검색해보니 대충 아래와 같은 것이 제일 만만해 보입니다.

먼저 스타벅스 매장 주소 데이터는 스타벅스 웹사이트에서 가져오면 되는데 csv나 엑셀 파일로 다운로드하게 지원하지 않으니 웹페이지의 내용물을 읽어서 파싱(parsing)해야 합니다.

이 작업은 웹 스크랩이라고 하는데 흔히 크롤이라고 부릅니다. 사실 원래 크롤과 스크랩은 서로 차이가 많습니다만 이 글에서는 그냥 널리 알려진 대로 크롤이라고 하겠습니다.

또 스타벅스 웹페이지는 지도상에 매장의 위치를 표시하기 위해서 Ajax로 데이터를 호출하는 방식을 사용하는데 이것의 방식을 알아내서 어떻게 데이터를 가져와야 하는지도 알고 있어야 합니다.

보통 크롬이나 파이어폭스의 개발자도구를 사용해서 알아내는데 연습과 방법을 익히는데 시간과 노력이 필요합니다. IT활용능력, 웹개발능력과 관련이 있는 것이라서 이것까지 설명하려면 설명이 길어지므로 이것을 알아내는 방법은 여기서는 생략합니다. 다음 기회에 다른 포스트에서 하겠습니다.

이 글에 소스코드를 넣어 두었으니 이미 완성된 코드를 참고 참고하세요.

인구데이터는 국가통계포털 웹사이트에서 가져오면 됩니다. csv나 excel로 다운로드 할 수 있게 지원하므로 매우 편합니다만 그래도 역시 데이터 정제, 정돈 작업은 좀 필요합니다.

인구데이터는 여기 말고도 받을 수 있는 곳이 더 있습니다. 더 편한곳이 있으면 그냥 거기를 이용하세요.

데이터를 크롤하는 법

R언어는 텍스트 파일을 파싱하거나 json 데이터를 처리하는 것이 조금 복잡한 편에 속합니다. R언어가 수치와 벡터 계산에 중점을 두기 때문에 이런 텍스트 처리에는 매우 약합니다.

하기가 까다롭다는 말입니다.

이런 것을 하려면 Python이나 Javascript가 더 나은 선택이지만

R언어로 이걸 하면 한 코드에서 데이터 크롤, 정제, 분석, 시각화까지 한 번에 할 수 있어 관리가 편하고 나중에 코드를 다시 볼 때 전체 흐름을 이해하기 편하다는 장점이 있습니다.

어쨌든 R언어에서 httr, urltools, jsonlite 패키지의 사용법을 익히면 됩니다.

데이터 정제, 정돈

데이터랭글링이라는 것을 할 텐데 이런 작업은 tidyverse 패키지에 포함된 여러 패키지를 이용하는 것이 가장 세련되고 좋은 방법입니다.

그리고 시각화는 ggplot2를 사용하겠습니다.

결과

스타벅스 전체 매장수: 1658개

1658개로 나옵니다. 스타벅스는 모두 신세계에서 운영하는 직영점이라고 알려져 있고 관리가 웹사이트 관리가 잘 되고 있을 것이라고 생각돼서 이 수치는 맞을 것입니다.

수천개는 될 것 같지만 제 예상보다는 적습니다.

시도별 매장수

   sido               n
   <chr>          <int>
 1 서울특별시       571
 2 경기도           383
 3 부산광역시       128
 4 대구광역시        71
 5 인천광역시        67
 6 경상남도          66
 7 광주광역시        59
 8 대전광역시        59
 9 경상북도          48
10 충청남도          36
11 울산광역시        29
12 전라북도          29
13 강원도            28
14 충청북도          26
15 전라남도          25
16 제주특별자치도    22
17 세종특별자치시    11

시도별 매장수는 역시 서울이 가장 많고 그 다음은 경기도, 부산 순입니다. 인구수가 많은 시도에 매장이 더 많은 것을 알 수 있습니다.

네 당연하죠. 물론 지역별로 로컬 카페나 다른 프렌차이즈, 브랜드의 커피매장이 더 인기가 있는 지역이 있을 수도 있으니 이 가설이 틀렸을 여지는 있습니다.

차트로 보며 시도별 매장수의 규모를 더 직관적으로 확인할 수 있는데 서울특별시와 경기도를 합치면 다른 시도를 다 합친 것 보다 더 많습니다.

우리나라 스타벅스 매장은 수도권에 크게 집중되어 있습니다.

구군별 매장수

   sido       gugun        n sido_gugun           
   <chr>      <chr>    <int> <chr>                
 1 서울특별시 강남구      88 서울특별시   강남구  
 2 서울특별시 중구        52 서울특별시   중구    
 3 서울특별시 서초구      48 서울특별시   서초구  
 4 경기도     성남시      47 경기도   성남시      
 5 경기도     고양시      42 경기도   고양시      
 6 서울특별시 영등포구    40 서울특별시   영등포구
 7 서울특별시 종로구      39 서울특별시   종로구  
 8 경기도     수원시      35 경기도   수원시      
 9 경기도     용인시      34 경기도   용인시      
10 서울특별시 송파구      34 서울특별시   송파구  

구군별로 매장수를 살펴보면 역시 서울 강남구가 가장 많습니다. 한국에서 아마 인구밀도가 가장 높은 지역이며 유동인구와 일과시간에 직장인이 가장 많이 있는 곳입니다.

그 다음은 역시 유동인구가 가장 많고 오피스밀집 지역이며 인구밀도가 높은 서울 중구입니다. 중구에는 명동, 충무로, 을지로가 있습니다.

그 다음은 서울 서초구인데 서초구는 강남구와 인구밀도는 비슷하지만 오피스공간 보다는 주거지역이 훨씬 많기 때문에 강남구나 중구에 비해서 낮시간에 직장인이 아주 많지는 않습니다. 그래도 괘나 많습니다.

그리고 그 다음은 경기도 성남시인데 성남에는 분당과 판교가 있으며 성남전체의 인구도 많으므로 이것도 당연합니다.

서울-강남구에 스타벅스가 압도적으로 많으며 다른 대형 지역구들도 제법 많습니다. 앞서 말했듯이 이 지역은 직장인들이 주중에 많이 머무는 지역들입니다. 물론 여기 나온 인구수는 거주 인구수이기 때문에 직장인이 주중에 많다는 것과의 상관관계를 다시 확인해야 할 필요가 있습니다만 알려진 바로는 매우 특별한 경우를 빼면 거주인구가 많은 지역에 직장도 많습니다.

인구수와 매장수의 상관관계

	Pearson's product-moment correlation
data:  sido_tbl$n and sido_tbl$total
t = 7.6707, df = 15, p-value = 1.439e-06
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.7216524 0.9609928
sample estimates:
      cor 
0.8926678 

시도별 인구수와 스타벅스 매장수의 상관관계를 보면 상관계수가 0.89로 강한 상관이며 검정에서 대립가설 채택으로 상관이 있다는 결과가 나옵니다.

더 해볼 것

상관관계를 볼 때 단순 인구수가 아닌 면적을 인구수로 나눠서 인구밀집도를 구한 다음에 상관관계를 보는 것이 더 합리적이겠지만 면적데이터를 가져와서 붙여야 하니 귀찮아서 생략하겠습니다.

또 구군별로도 면적데이터를 가져와서 구군별로도 인구수와 스타벅스 매장수가 상관이 있는지 확인해 보는 것도 필요합니다만 이것도 생략하겠습니다.

혹시 해보고 싶으시면 아래 소스를 보시고 고쳐서 직접 한 번 해보세요.

결론 및 인사이트

매장수와 지역의 인구밀집도는 관계가 있습니다.

시도별, 구군별로 모두 상관관계가 있습니다.

따라서 스타벅스매장이 많은 곳은 인구밀집도가 높은 지역일 가능성이 큽니다.

인구밀집도는 유동인구수와 상관관계가 있는 것으로 알려져 있습니다.

스타벅스 매장이 많은 곳(있는 곳)은 유동인구가 많은 곳입니다.

마약 누군가 많은 유동인구가 필요한 점포를 개업하려고 한다면 스타벅스 부근에 개점하면 됩니다.

길거리 음식을 팔거나 판매하려면 패션 팟업스토어, 푸드 트럭을 열려면 스타벅스 근처에 하면 잘 될 것입니다. 물론 그게 가능하다면 말이죠.

전체 스크립트

소스 파일

https://github.com/euriion/r-exams/blob/main/starbucks_analysis.R

소스 내용

# 스타벅스 목록을 읽어서 간단한 통곗값을 출력하는 R 스크립트
# install.packages(c("httr", "urltools", "jsonlite"))
library(httr)
library(urltools)
library(jsonlite)
# 데이터를 긁어올 사이트: https://www.starbucks.co.kr
# 시도 목록을 가져오는 부분
url <- "https://www.starbucks.co.kr/store/getSidoList.do"
res_content <- POST(url)
res_object <- fromJSON(content(res_content, "text"))
sido_items <- tibble(sido_nm=res_object$list$sido_nm, sido_cd=res_object$list$sido_cd)
# 구군 목록을 가져오는 코드. 구군은 분석에 필요하지 않으므로 리마킹
# url = "https://www.starbucks.co.kr/store/getGugunList.do"
# post_params = "sido_cd=01&rndCod=4X93H0I94L"
# res_content <- POST(url, body=parse_url(sprintf("%s?%s", url, post_params))$query)
# res_object <- fromJSON(content(res_content, "text"))
# 각 시도별 주소를 가져오는 부분
url <- "https://www.starbucks.co.kr/store/getStore.do"
payload <- "in_biz_cds=0&in_scodes=0&search_text=&p_sido_cd=08&p_gugun_cd=&in_distance=0&in_biz_cd=&isError=true&searchType=C&set_date=&all_store=0&whcroad_yn=0&P90=0&new_bool=0&iend=1000"
post_params <- parse_url(sprintf("%s?%s", url, payload))$query
res_content <- POST(url, body = post_params)
res_object <- fromJSON(content(res_content, "text"))
# 시도 코드와 이르을 받아서 주소목록이 들어 있는 data.frame을 리턴하는 함수
get_addrs <- function(sido_cd, sido_nm) {
  url <- "https://www.starbucks.co.kr/store/getStore.do"
  payload <- sprintf("in_biz_cds=0&in_scodes=0&search_text=&p_sido_cd=%s&p_gugun_cd=&in_distance=0&in_biz_cd=&isError=true&searchType=C&set_date=&all_store=0&whcroad_yn=0&P90=0&new_bool=0&iend=10000", sido_cd)
  post_params <- parse_url(sprintf("%s?%s", url, payload))$query
  res_content <- POST(url, body = post_params)
  res_object <- fromJSON(content(res_content, "text"))
  cbind(rep(sido_cd, length(res_object$list$addr)), rep(sido_nm, length(res_object$list$addr)), res_object$list$addr)
}
# 데이터랭글링
# install.packags(c("tidyverse", "stringi"))
library(tibble)
library(dplyr, warn.conflicts = FALSE)
library(stringi)
df <- sido_items |> rowwise() |> mutate(addr=list(get_addrs(sido_cd, sido_nm)))
tbl <- do.call(rbind.data.frame, df$addr)
names(tbl) <- c("sido_cd", "sido_nm", "addr")
tbl <- tbl |> rowwise() |> mutate(addrsplit=stri_split_fixed(addr, " ", 4))
tbl <- as_tibble(do.call(rbind.data.frame, tbl$addrsplit))
names(tbl) <- c( "sido", "gugun", "dong", "bunji")
tbl |> count()  # 전체 매장 개수
stat_sido <- tbl |> count(sido) |> arrange(desc(n))  # 시도별 매장 수
stat_gugun <- tbl |> count(sido, gugun) |> arrange(desc(n)) |> mutate(sido_gugun=paste(sido, " ", gugun))  # 시도별 매장 수
# 시각화
library(ggplot2)
p <- ggplot(stat_sido, aes(x =reorder(sido, n), y = n)) +
  geom_col(aes(fill = n), width = 0.7)
p <- p + coord_flip()
p <- p + xlab('시/도')
p
p <- ggplot(head(stat_gugun, 20), aes(x =reorder(sido_gugun, n), y = n)) +
  geom_col(aes(fill = n), width = 0.7)
p <- p + coord_flip()
p <- p + xlab('구/군')
p
# 데이터 출처: # https://kosis.kr/statHtml/statHtml.do?orgId=101&tblId=DT_1B040A3&checkFlag=N
pop_sido_text_data <- "sido,total,male,female
서울특별시,9505926,4615631,4890295
부산광역시,3348874,1638207,1710667
대구광역시,2383858,1174667,1209191
인천광역시,2949150,1476663,1472487
광주광역시,1441636,713037,728599
대전광역시,1451272,724026,727246
울산광역시,1121100,575939,545161
세종특별자치시,374377,186907,187470
경기도,13571450,6830317,6741133
강원도,1538660,774315,764345
충청북도,1597097,810548,786549
충청남도,2118638,1083242,1035396
전라북도,1785392,888291,897101
전라남도,1832604,922190,910414
경상북도,2624310,1322509,1301801
경상남도,3311438,1666968,1644470
제주특별자치도,676691,339071,337620
"
pop_sido_tbl <- read.csv(textConnection(pop_sido_text_data))
sido_tbl <- pop_sido_tbl |> inner_join(stat_sido, by = "sido")
# 상관계수
cor(sido_tbl$n, sido_tbl$total)
# 결괏값: 0.8926678
cor.test(sido_tbl$n, sido_tbl$total)

여기까지 입니다.

R ARIMA 예제 코드

R의 ARIMA 모형의 예제입니다.
서버의 메모리의 사용량의 추이를 보고 얼마 후에 고갈되는지를 예측하는 코드입니다.
물론 예측력은 많이 떨어지고 현실성이 없을 수 있습니다.

# -------------------------
# Memory usage forecasting
# -------------------------
library(stats)
arima(lh, order = c(1,0,0))
arima(lh, order = c(3,0,0))
arima(lh, order = c(1,0,1))

arima(lh, order = c(3,0,0), method = "CSS")

arima(USAccDeaths, order = c(0,1,1), seasonal = list(order=c(0,1,1)))
arima(USAccDeaths, order = c(0,1,1), seasonal = list(order=c(0,1,1)),
method = "CSS") # drops first 13 observations.
# for a model with as few years as this, we want full ML

arima(LakeHuron, order = c(2,0,0), xreg = time(LakeHuron)-1920)

## presidents contains NAs
## graphs in example(acf) suggest order 1 or 3
require(graphics)
(fit1 <- arima(presidents, c(1, 0, 0)))
tsdiag(fit1)
(fit3 <- arima(presidents, c(3, 0, 0))) # smaller AIC
tsdiag(fit3)

# ----- prediction part

od <- options(digits=5) # avoid too much spurious accuracy
predict(arima(lh, order = c(3,0,0)), n.ahead = 12)

(fit <- arima(USAccDeaths, order = c(0,1,1),
seasonal = list(order=c(0,1,1))))
predict(fit, n.ahead = 6)
options(od)

# ----- Arima
library(forecast)
fit <- Arima(WWWusage,c(3,1,0))
plot(forecast(fit))

x <- fracdiff.sim( 100, ma = -.4, d = .3)$series
fit <- arfima(x)
plot(forecast(fit,h=30))

# ----- Arima forecast for memory usage (unit %) -----
library(forecast) # need to install the package "forecast"
memory.usage.threshold <- 100 # 100%
memory.usage.forecast.period <- 30 # 미래 30일분까지 예측
memory.usage.observations.startdate <- "2012-09-01"
memory.usage.observations <- c(10,11,30,35,36,39,48,56,75,69,68,72,71,72,83) # 관측치 12일분

memory.usage.period <- seq(as.Date(memory.usage.observations.startdate), length=length(memory.usage.observations), by="1 day") # 날짜세팅
memory.usage.df <- data.frame(row.names=memory.usage.period, memory=memory.usage.observations) # data.frame으로 변환
memory.usage.ts <- ts(data=memory.usage.df) # time series 생성
memory.usage.model <- auto.arima(memory.usage.ts) # arima 모델 생성
memory.usage.forecast <- forecast(memory.usage.model, h=memory.usage.forecast.period) # forecast 결과 생성
memory.usage.forecast.df <- as.data.frame(memory.usage.forecast) # forecast 결과 변환

d = memory.usage.threshold,][1,])) # 100 이 넘는 최초 데이터 추출
if(is.na(d)) {
print(sprintf("앞으로 %s일동안 %s%% 초과하지 않음", memory.usage.forecast.period, d - length(memory.usage.observations)))
} else {
print(sprintf("%s일 후에 %s%% 초과됨", d - length(memory.usage.observations), memory.usage.threshold))
}

# ---- 시각화(Plotting)
plot(memory.usage.forecast) # plotting
abline(h=100, col = "red", lty=3)
abline(v=d, col = "red", lty=3)

library(ggplot2)
library(scales)

plt <- ggplot(data=pd,aes(x=date,y=observed))
p1a<-p1a+geom_line(col='red')
p1a<-p1a+geom_line(aes(y=fitted),col='blue')
p1a<-p1a+geom_line(aes(y=forecast))+geom_ribbon(aes(ymin=lo95,ymax=hi95),alpha=.25)
p1a<-p1a+scale_x_date(name='',breaks='1 year',minor_breaks='1 month',labels=date_format("%b-%y"),expand=c(0,0))
p1a<-p1a+scale_y_continuous(name='Units of Y')
p1a<-p1a+opts(axis.text.x=theme_text(size=10),title='Arima Fit to Simulated Datan (black=forecast, blue=fitted, red=data, shadow=95% conf. interval)')

원본 소스코드는 아래에 있습니다.

https://github.com/euriion/code_snippets/blob/master/R/forecast_exam.R

2SD rule (2표준편차 법칙)

2SD Rule (2표준편차 법칙, To understand variability; 가변성을 이해하기?)

Super crunchers(슈퍼크런처, Ian Aires저) 라는 책에 있는 내용 중 2SD라는 용어가 나와서 궁금하던 차에 내용 일부를 정리해 둡니다. 

슈퍼크런처는 기술서적이라기 보다는 교양서적입니다. “직관보다는 데이터분석에 입각한 의사결정에 중점을 두는 것이 옳다”라는 내용인데요.  전문가의 직관에 의존한 의사결정도 좋지만 데이터에 입각한 근거있는 결정도 필요하다는 내용입니다.  데이터마이닝에 관심이 있는 사람은 한 번 읽어봐도 좋을 듯 싶습니다. 어려운 내용은 아닙니다만 생각을 좀 해야 하는 부분은 있습니다.

이 책에 통계에 대한 내용이 나옵니다.

2표준편차법칙(2SD rule)은 책의 후반부에 나오는데 잘 보지 못하던 용어라서 여기저기서 내용을 찾아서 정리해 두려는 것입니다.

정보를 찾다 보니 이미 잘 알려진 내용을 다른 방식으로 표현한 것이고 일반적으로 생활에서 어떻게 쉽게 적용이 가능한지 보여주는 일종의 단편적인 예와 같은 것입니다.

2SD는 어떤 정규분포변수가 그 평균치의 -2 표준편차와 -2표준편차내에 위치할 확률이 95%가 된다는 말입니다. 여기서 ±2SD는 2의 의미는 1.96을 반올림해서 나온 근사치값으로 보입니다. 그래서 ±3SD rule (2.54를 반올림해서 나온것이라고 생각하는데 반올림하기에는 반올림한 차이가 좀  큰 것 같아서 조금 그렇네요)일 경우에는 99% 라고 계산하는 계산식도 자료를 찾던 중 쉽게 찾아 볼 수 있었습니다.

제가 가진 통계학 책들에는 2SD, 3SD라는 용어를 사용하지 않았기 때문에 무슨 용어인가 궁금 했습니다만 생각보다 단순한 것이었습니다. 용어는 저자가 직접 만들어낸 것 같기도 한데 정확하게 저 용어를 사용해도 되는지는 모르겠습니다. 저자가 유명한 사람이고 하다보니 이런 용어 새로 만든다고 해서 뭐라고 할 사람은 별로 없을 것 같습니다. 영문 자료를 찾다 보니 책의 영향인지는 모르겠지만 같은 용어를 쓰는  분들도 더러 있었고 아닌 경우에도 대충 감을 잡을 만한 비슷한 내용을 생각보다 많이 볼 수는 있었습니다. 아래는 찾은 내용들과 책을 정리한 내용을 잊어 버릴까봐 기록해 둔 것입니다.

2SD rule: 정규분포변수가 그 평균치의 +-2표준편차 내에 존재할 확률은 95%이다.

먼저 슈퍼크런처 책에 나와 있는 내용들중 예제를 Q/A형식으로 그대로 조금 거쳐서 적은 것입니다.

책의 질답 내용

Q. 사람의 IQ는 평균이 100이고 표준편차가 15인 정규분포를 따른다. 그러면 사람들의 IQ는 대체 얼마라는 말인가?

A. 2SD 법칙에 의하면 95%의 사람들의 IQ는 70(100에서 2표준편차를 뺀값)에서 130(100에서 2표준편차를 더한 값, 즉 15×2=30) 사이에 존재한다. 2SD 법칙을 사용하면 표준편차값을 분산도에 대한직관적인 표현으로 쉽게 변환할 수 있다. 대부분의 사람들은 IQ가 70 ~ 130사이이다. IQ의 분산도가 더 적다면, 즉 표준편차가 5라면 95%의 사람들을 포함하는 IQ의 범위는 훨씬 작아진다.  다시 말해 95%의 사람들이 IQ가 90과 110사이라고 말할 수 있다.

Q. 유권자 1243명을 대상으로한 퀴니피액대학 여론조사에서, 상원의원 후보자 가운데 캘빈이 52%로 48%인 홉스보다 지지율이 높게 나왔다. 이번 조사의 오차 한계는 +-2% 포인트이다. 캘빈이 당선된다는 말인가? 아니라는 말인가?

A. 오차한계는 2SD에 해당한다. 오차한계가 2%포인트라고 나왔다면 이것은 1표준편차(1SD)가 1% 포인트라는 뜻이다. 우선 캘빈의 표준 지지율 52%에서 오차한계(즉 2SD)를 더하고 빼면 특정한 범위가 나온다. 즉 52 ±2% 이다. 2SD rule에 의하여 캘빈을 지지하는 유권자가 50% ~ 54% 사이일 확률이 95%라는 말이다. 캘빈의 지지율이 50% ~ 54%일 확률이 95%이므로 그의 실제 지지율이 종형곡선(정규분포, Normal distribution의 그래프)의 양쪽 가장자리에 속할 확률, 즉 54%이상 50%이하일 확률은 5%가 된다. 그리고 종형곡선의 양쪽 가장자리 부분은 면적이 동일하므로(정규분포 곡선은 아시다시피 좌우 대칭입니다), 캘빈의 지지율이 50%이하가 될 확률은 2.5%이다. 이는 캘빈이 앞서고 있을 확률이 97.5%라는 뜻이다.

Q. 레이번이 셜리를 51% 대 49%, 오차한계 2%로 앞서고 있는 경우, 신문은 이 경합을 두고 ‘통계적으로 볼 때 막상막하’라고 보도할 것이다. (맞는 말인가?)

A. (하지만) 이건 헛소리다. 레이번의 여론조사 결과는 50%에서 1표준편차만큼 높다(오차한계는 2표준편차이므로 1표준편차는 1%). 따라서 레이번이 앞서고 있을 확률이 84%라는 것을 알게 된다. 별다른 변수가 없다면 레이번의 당선이 유력한 것이다. 누구를 지지할지 결정 못한 사람들과 제3의 후보들이 있기 때문에, 많은 여론조사에서 경합을 벌이는 두 후보의 지지율은 합해도 100%가 되지 않는다. 그러나 지지율 우세 확률은 우리에게 예상 당선자를 말해준다.

※ 그럼 이제 책에 나와 있는 몇가지 정의를 다시 적어 둡니다.

통계적의 유의하다. 통계학자들이 어떠한 결과를 두고 ‘통계적으로 유의하다’ 라고 말하는 경우, 어떤 예측값이 다른 값과 2표준편차 이상 차이가 난다는 것을 뜻한다.

2SD의 또다른 측면 임의변수가 예상 평균값에서 2표준편차 이상 떨어져 있을 확률은 5%가 채 되지 않는다. (2SD rule에 의하면 95%가 평균으로부터의 2SD내에 있으므로 나머지는 5%가 됩니다)

오차범위 오차범위는 신뢰구간이 95%라면 그 95% 바깥쪽 넓이를 말하는 것이 아닙니다. 95%신뢰구간이라고 하면 이 신뢰 구간 안에 포함될 (표본이 아닌 실제값이) 확률이 95%인 것입니다.

다시 말해서 신뢰구간은 ‘확률±오차범위’ 에 포함될 확률이 95% 임을 말합니다. 물론 2SD를 따른다고 하는 경우에 그렇습니다. 아래는 무한모집단에서 표본허용오차 공식 ±1.96sqrt(p(1-p)/n) 또는 Z crit * stdev / sqrt(n) 표본수가 매우 큰 경우 즉, 무한모집단이면 보통 모집단은 정규분포(Normal Distribution, Gaussian Distribution)를 따르는 것으로 가정합니다.  자연계에서 아주 자연스러운 데이터의 확률 분포는 정규분포를 보편적으로 따릅니다. 그리고 샘플링의 경우 중심극한원리 (Central Limit Theorem)에 의해서 표본(Sample)의 양이 충분하다면 정규분포를 따르게 됩니다 여기서 표본 오차를 구하려면 표본집단에서의 표준편차나 모집단의 편차가 필요합니다.

모집단의 표준편차가 없는 경우 표본집단의 표준편차를 이용하게 되는데 보통의 경우 모집단의  표준편차(또는 분산)을 구할 수(알 수) 없는 경우가 대부분입니다. 아래는 여기저기서 긁어 모은 오차범위를 구하는 것에 대한 예제풀이입니다.

(워낙 마구잡이로 긁어오다 보니 출처가 엉망이 되었고 내용을 제가 많이 편집해서 출처는 따로 기재하지 못했습니다. 혹시 문제가 된다면 알려주시면 바로 지우도록 하겠습니다.)

예제

n(표본수) = 1000명 일 경우

보통 여론조사에서 1000명 많이 한다고 합니다. 그러고보니 뉴스에서 1000을 대상으로 했다고 하는 것을 많이 들은 것도 같지요. 하지만 1000명으로 고정되어 있는 것은 아닙니다.

(±)1.96 * sqrt(0.25/1000) = 0.0309. 여기에 100을 곱하면 3.09 1000명일 경우는 표본오차가 대략 (±)3.1% n(표본수) = 600이라면 같은 방식으로 계산해서 (±)4.0%

0.50.5를 하는 이유 여기서 0.50.5는 최대허용오차(중요)로 0.50.5=0.25로 보다 큰 숫자를 가지는 경우는 없습니다. 생각해 보니 진짜 그러네요. 0.1 * 0.9, 0.2 * .08, 0.3 * 0.7, … 모두 0.25보다는 작습니다)

예를 들어 0.4 * 0.6 = 0.24로 0.25보다 작으므로 0.50.5를 두고 최대허용오차라고 합니다. 언론에서 말하는 (±)3.0, (±)4.0 등도 모두 최대허용오차입니다.

1.96을 곱하는 이유 정규분포의 특성에 따르면, 신뢰구간 95%일 경우는 1.96을 곱하고, 99%일 경우는 2.54를 곱합니다. (위에서 말씀드렸지만 2SD(1.96)는 95%이고 3SD(2.54)는 99%입니다) 즉, 확률을 높일 경우 정규분포에서 보는 신뢰구간이라고 정의하는 영역이 넓어지게 됩니다.

위의 예(n = 1000)에서 1.96(95% 신뢰구간) 대신에 2.54(99% 신뢰구간)를 곱하면, 99%의 신뢰구간에서 (+-)4.0%가 됩니다.

추가 설명

보통 지지율이나 여론 조사 등에서 이 오차범위는 1.96*0.25/sqrt(n) 으로 구하는데, 당연히 n이 클수록 이 값이 작게 나오며 표본을 많이 뽑을 수록 오차범위가 줄어들어 좋습니다. 예를 들어보면, A후보가 49% B후보가 46% 지지율을 얻고 오차범위가 ±1.0%라고하면 A후보는 48%~50%의 지지율을 얻고, B후보가 45%~47%의 지지율을 얻을 확률이 95%라는 것이죠.

위의 책에서의 예제와 같은 것입니다. 확률이 두 번 겹쳐서 나오니까 헷갈리지만 그리 어렵지 않습니다. 어쨌든 여기서는 95%신뢰구간 하에서는 A후보가 B후보를 이기고 당선될 것이라고 생각할 수 있죠. A후보의 지지율 중 낮은 영역에 있는 48%가 B후보의 지지율 중 높은 영역에 있는 47%보다 크기 때문에 95%확률로 A후보가 당선된다는 얘기입니다. 물론 5%의 오차가 있기 때문에 아닐 수도 있겠지만 가능성이 적겠지요.

100%신뢰구간은 가능한가?

100% 신뢰구간은 -무한대에서 +무한대가 되어야 해서 현실에서는 성립이 안됩니다.

사용할 수가 없고 구간을 정할 수도 없는 것이지요. 보편적으로 신뢰구간을 정해야 하는 경우 대부분 95%신뢰구간을 사용하고 특별히 더 정확하게 알고 싶은 경우나 투표의 경합지역 같이 민감한 경우는 좀더 신뢰성을 높이기 위해서  99%를 사용하기도 한다고 합니다.

실제로 정규분포를 R이나 Prism, Excel등의 툴들을 이용해서 그래프를 그려 놓고 보시면 이해가 빠르니 시간날 때 한 번 그려보세요

포맷이 깨져서 다시 수정하다보니 틀린 내용이 있어서 일부 교정을 했습니다. 역시 전 멍청한가봐요.