[이 게시물은 Dustin의 깃허브 블로그에도 게재되어 있습니다.]
Couchbase Server에서 수집하는 데이터를 이해하는 데 도움을 주기 위해 R을 사용한 작업 중 일부를 설명하고자 합니다.† 꽤 흥미롭고 유용하며 쉽다고 생각하기 때문입니다. 하지만 청중이 누구일지 모르기 때문에 좋은 출발점을 찾기가 어려웠습니다. 즉, 시작하기에 적합한 가정을 찾는 것이 상당히 어려웠습니다.
그런데 지난주에 미디어 회사의 한 멋진 분과 이야기를 나눴는데, 그분은 구체적인 질문을 하셨습니다: "분석가들이 제가 저장하고 있는 모든 멋진 데이터를 어떻게 보고할 수 있을까요? 카우치베이스?" 저는 더 깊이 파고들었습니다. 이 분석가들은 누구일까요? 어떤 도구를 사용하나요?
† 증분 맵 축소 보기는 Apache CouchDB 보기와 동일하므로 모든 것이 CouchDB에서도 작동합니다.
내 대상
알고 보니 분석가들은 제가 상상하는 것과 꽤 비슷했습니다. 그들은 종종 오라클의 데이터 웨어하우징 도구를 사용하는데, 이 도구는 모든 종류의 훌륭한 마법을 부리지만, 자신이 익숙한 범위를 벗어나면 정말 크게 넘어지곤 합니다. 그냥 무시해도 될 것 같았습니다. 하지만 그는 제게 확고한 발판을 마련해 주는 말을 했습니다. 프로그래머는 아니지만, 그들은 R 를 데이터 분석의 일부로 사용합니다.
이 질문은 자신의 데이터를 가져오는 방법을 알고 싶어하는 CouchBase 사용자가 질문한 것이므로, 이 글을 읽는 사람은 CouchBase보다 R을 조금 더 잘 알고 있다고 가정하겠습니다.
보기 정보
카우치 뷰 개념을 이해하려면 읽을 수 있는 자료가 많이 있습니다. 보기 챕터의 카우치베이스 서버 매뉴얼 에 이 개념이 잘 설명되어 있습니다. 모든 것을 알고 싶다면 자세히 살펴보세요. 하지만 대부분의 사용 사례에서는 세 가지로 요약할 수 있습니다:
- 유용한 정보를 추출합니다.
- 분류하고 좋아하는 것을 한데 모아보세요.
- 기본적인 집계를 수행합니다.
이것이 제가 대부분의 경우 많은 데이터를 유용한 정보로 전환하는 방법입니다. 다음 예시가 여러분도 그렇게 하는 데 도움이 되기를 바랍니다.
데이터
데이터 그로킹 튜토리얼에서 가장 어려운 부분은 데이터에 관한 내용이 전혀 없다는 것입니다. 따라서 독자의 흥미를 떨어뜨리는 동시에 자신의 문제에 적용하기가 조금 더 어렵게 만드는 경우가 많습니다.
안타깝게도 제가 정기적으로 리포팅을 위해 추출하는 가장 흥미로운 데이터는 다소 민감하기 때문에 제가 가장 많이 활용했던 데이터를 공유할 수는 없지만, 이 글이 여러분에게 흥미로운 정보를 제공하는 데 도움이 되기를 바랍니다.
제가 작업하기 위해 선택한 데이터는 SFPD에서 보고된 인시던트 세트입니다. SF 데이터 웹사이트 웹사이트를 방문하세요. 2003년 이후 SFPD가 보고한 거의 모든 내용을 담고 있습니다.
이러한 문서는 매우 규칙적이고 평평합니다. 데이터는 더 복잡할 수 있지만 기법은 동일합니다. SFPD 데이터 세트의 예제 문서부터 살펴보겠습니다:
"카테고리": "매춘",
"인시던트_ID": 90096348,
"지구": "안심",
"타임스탬프": “2009-01-27T04:03:00”,
"lon": -122.416261836834,
"lat": 37.7853750846376,
"위치": "오파렐 스트리트/하이드 스트리트",
"시간": "04:03",
"날짜": "2009-01-27",
"해결": "체포, 인용",
"day": "화요일",
"desc": "매춘의 집을 방문하도록 권유"
}
이 모든 것이 무엇인지 이해할 수 있을 것 같으니 이제 시작하겠습니다.
R로 데이터 가져오기
여기서 사용할 패키지가 몇 가지 있으므로 시작하기 전에 R에 해당 패키지를 설치해 보겠습니다:
install.packages(c('rjson', 'ggplot2', 'reshape'),
의존성=TRUE)
require(rjson)
require(reshape)
require(RColorBrewer)
require(ggplot2)
R은 "정사각형" 데이터를 좋아하기 때문에 뷰의 출력을 매우 규칙적으로 만드는 경향이 있는데, 이는 뷰를 가져와서 다시 가져오는 매우 간단한 함수를 사용할 수 있다는 의미이기도 합니다. 이를 위해 제 R 스크립트에는 다음과 같은 몇 가지 기본적인 공통 설정이 있습니다:
# 카우치베이스 뷰 베이스를 가리키는 포인터입니다. 여기에서
# 자체 데이터
urlBase
# 이것은 기본 GET 요청 -> 파싱된 JSON입니다.
getData fromJSON(file=paste(urlBase, sub경로, sep="))$rows
}
# 그리고 이를 데이터 프레임으로 평평하게 만들고, 선택적으로 이름을
# 열.
getFlatData b if (!is.null(n)) {
names(b) }
b
}
# 또한 요일제로 작업할 예정이므로 이것도 필요합니다:
다우 '목요일', '금요일', '토요일')
쇼트다운
전체 범죄 신고 건수
대부분의 데이터 집합이 그렇듯이 어디서부터 시작해야 할지 모르겠으니 우선 어떤 종류의 범죄가 있는지 살펴보기로 하겠습니다. 저는 총 건수와 요일별 건수가 궁금합니다. 좋은 점은 소파 뷰를 사용하면 이 두 가지를 모두 알려주는 단일 뷰를 만들 수 있다는 것입니다. 뷰 소스를 살펴보겠습니다:
emit([doc.category, doc.day], null);
}
정말 단순해 보이지만 _count내장 감속기를 사용하면 그룹화할 때 많은 깔끔한 작업을 수행할 수 있습니다. 와 그룹_레벨=1를 사용하면 카테고리별 범죄 수를 얻을 수 있습니다. 이를 플로팅하여 인기 있는 항목을 살펴봅시다. 이 데이터를 다음과 같은 디자인 문서에 저장했다고 가정합니다. 카테고리의 뷰 이름으로 bydayR에게 이렇게 말하세요:
# 카테고리와 각 카테고리의 개수가 포함된 데이터 프레임을 가져옵니다.
cat c('cat', 'count'))
# 열이 문자열로 돌아오며 유용하게 사용하려면 수정이 필요합니다.
cat$cat cat$count # 또한 개수별로 정렬하면 이해하기 쉬웠습니다.
cat$cat
# 이제 플롯합니다.
ggplot(cat, aes(cat, count, alpha=count)) +
geom_bar(fill='#333399′, stat='identity') +
scale_alpha(to=c(0.4, 0.9), legend=FALSE) +
scale_y_continuous(formatter="쉼표") +
labs(x=", y=") +
opts(title='총 범죄 신고 건수') +
coord_flip() +
theme_bw()
그러면 R이 이걸 알려줍니다:

요일별
이 점이 다소 흥미로워서 요일별 분포가 어떻게 되는지 알고 싶었습니다. 위와 동일한 뷰를 다음과 같이 사용할 수 있습니다. 그룹_레벨=2를 사용했지만 비율이 엄청나게 다르기 때문에 R에서 요일별로 각 카테고리에 대한 데이터 프레임의 상대적 분산을 계산한 다음 이를 플로팅하도록 했습니다. 다음은 R 코드입니다:
# 동일한 데이터를 요일별로 구분하여 가져옵니다.
cat_byday c('cat', 'day', 'count'))
# 위와 비슷한 수정 작업을 수행 중이지만 다른 주문 및
# 요일별 몇 가지 보기(장난용으로 많이 사용)
cat_byday$cat cat_byday$cat cat_byday$count cat_byday$cat_by_count cat_byday$day cat_byday$shortdow
# 요일별 각 카테고리의 백분율을 계산합니다.
A1 A2 합계)
total_per_day cat_byday$perc
# 어떤 모습인지 보겠습니다.
ggplot(cat_byday, aes(shortdow, cat, size=perc, alpha=perc)) +
geom_point(color=’#333399′) +
scale_alpha(legend=FALSE) +
scale_size(legend=FALSE) +
scale_y_discrete(limits=rev(levels(cat_byday$cat))) +
labs(x=", y=") +
theme_bw()
설정이 많은 것처럼 보이지만 대부분 유형 설정과 같은 것들입니다. 아래에서 일부를 재사용하겠습니다. 하지만 이 시점에서 살펴볼 것이 있습니다:

집중된 하위 집합
여기에는 많은 데이터가 있고 모두 상대적이어서 어떻게 비교해야 할지 알기 어려웠습니다. 저는 몇 가지 영역을 실제로 살펴보고 어떤 종류의 상관관계가 있는지 알아보고 싶었습니다. 이미 데이터가 로드되어 있었기 때문에 이미 요청된 데이터의 하위 집합을 가져와 패싯 플롯을 만들면 된다고 생각했습니다.
# 몇 가지 관심 카테고리를 선택하세요.
흥미로운 '매춘',
'술 취함',
'무질서한 행위')
# 이 하위 집합만 추출하고 카테고리를 리팩터링합니다.
sex_and_drugs sex_and_drugs$cat
ggplot(sex_and_drugs, aes(shortdow, count)) +
facet_wrap(~cat, scales='free_y') +
geom_bar(fill='#333399′, stat='identity') +
scale_y_continuous(formatter="쉼표") +
labs(x=", y=") +
opts(title='일별 범죄 보고서 선택') +
theme_bw()
그러면 이러한 개별 카테고리에 대해 훨씬 더 자세한 정보를 확인할 수 있습니다.

개인적으로 저는 알코올 관련 사건과 다른 사건 사이에 상관관계가 없다는 점이 매우 흥미로웠습니다. 술은 마약과 같은 역할을 하는 것 같습니다. 매춘부들은 술 취한 사람을 싫어할지도 모르죠. 누가 알겠어요...
시간 경과에 따라
이 시점에서 저는 데이터가 2003년으로 거슬러 올라간다는 사실을 깨달았고, 상황이 나아지고 있는지 나빠지고 있는지는 고려하지도 않았습니다. 자세히 살펴보지는 않았지만 상황이 나아지고 있는지 나빠지고 있는지를 빠르게 파악하고 싶었습니다. 다음은 연도별 및 카테고리별 인시던트 발생률을 알려주는 뷰입니다:
var ymd = doc.date.split('-');
emit([parseInt(ymd[0], 10), doc.category], null);
}
이 모든 예제에서와 마찬가지로, 저는 이것을 _count기본 제공 감소. 다음 R을 사용하여 연간 요율을 차트로 만들어 보겠습니다:
byyear c('year', 'count'))
byyear1TP4년
# 여기에는 2012가 충분하지 않으므로 이 차트에서는 이를 무시하겠습니다.
ggplot(byyear[byyear$year < 2012,], aes(year, count)) +
stat_smooth(fill=’#333399′) +
labs(y=", x=") +
scale_y_continuous(포맷터=쉼표) +
opts(title="연도별 총 인시던트 보고서") +
theme_bw()
이것은 우리에게 무엇을 알려주나요?

상황이 나아지고 있는 것 같습니다(또는 경찰이 더 게을러지고 있는 것 같습니다). 모든 카테고리에 해당되는지 좀 더 자세히 알아볼 수도 있지만, 그다지 관심이 없으니 다른 것을 살펴보도록 하겠습니다.
지역별 범죄
특정 범죄가 다른 범죄에 비해 어떤 지역에서 더 많이 발생하는지 알고 싶었습니다. 저는 문서의 district속성을 사용하여 (내장 좌표가 아닌) 히트맵에 대한 좋은 사용 사례가 될 수 있다고 생각했습니다.
한 가지 눈에 띄는 점은 일부 보고서에는 지구가 연결되어 있지 않다는 것입니다. 이 보고서에서는 이를 무시하기로 선택했지만, 구체적으로 고려하고 싶다면 사용자 지정 값으로 대체하는 방법을 쉽게 확인할 수 있습니다. 다음 보기 코드부터 시작해 보겠습니다:
if (doc.district != null) {
emit([doc.category, doc.district], null);
}
}
물론, 저희는 _count를 다시 내장했습니다. 여기서 한 가지 주목해야 할 점은 원래 플롯은 모두 데이터를 살펴본 후, 범죄 신고 건수가 1,000건 미만인 지역에는 관심이 없다고 결정했습니다. 이 지역은 출력 필터의 경우, 뷰가 구체화되어 있고 축소 적용 전에 맵 기능에 필터가 포함되어 있지 않았기 때문에 뷰를 요청할 수 있는 수단이 없기 때문에 R에서 이를 적용해야 했습니다. 이상적으로는 실제 뷰 요청에서 이 기능을 지원하면 좋겠지만, 당분간은 사후에 쉽게 추출할 수 있습니다:
by_region c('cat', 'region', 'count'))
by_region$카운트 by_region$지역
# 최소 1,000건 이상의 인시던트가 없는 것은 모두 무시
pop_regions 1000,]
팝_지역$캣 # 그리고 가장 인기 있는 범죄가 맨 위에 뜨게 하세요.
pop_regions$cat
ggplot(pop_regions, aes(x=region, y=cat, fill=count, alpha=count)) +
geom_tile() +
scale_fill_continuous('Incidents',
포매터=함수(x)
sprintf("%dk", x / 1000)) +
scale_alpha_continuous(legend=FALSE, to=c(0.7, 1.0)) +
labs(x=", y=") +
theme_bw() +
opts(title='지역별 범죄 유형',
axis.text.x=theme_text(각도=-90),
legend.position='오른쪽')
그러면 다음과 같은 히트맵이 나타납니다:

빈 영역은 2003년 이후 해당 지역에서 특정 유형의 범죄가 1,000건 이상 발생하지 않은 경우입니다. 연한 파란색 영역은 일부 사건이 발생했습니다. 밝은 빨간색이 가장 많습니다. 남쪽 지역은 피하고 싶은 것 같네요.
DA는 얼마나 많은 것을 거부하나요?
데이터의 일부에 대한 서버 측 집계를 가져오는 예로서, '지방 검사 기소 거부' 해결 유형이 특히 흥미로워서 이런 일이 얼마나 자주 발생하는지 알고 싶었습니다. 다시 간단한 뷰부터 시작하겠습니다:
emit([doc.resolution, doc.category], null);
}
그런 다음 일반적인 _count와 비슷합니다. 하지만 여기서 다른 점은 요청을 수행할 때 시작_키그리고 end_key매개 변수를 사용하여 이러한 방식으로 해결된 항목만 찾을 수 있습니다. 저는 해결 목록이 "지방 검사 기소 거부"에서 "예외적 허가"까지라는 것을 알고 있으므로 "Di"로 시작하고 "Dj"로 끝나는 것들만 찾을 수 있습니다. 이것들 역시 제가 방출하는 배열이므로 배열의 첫 번째 요소를 기반으로 합니다. 그러면 R 코드는 다음과 같습니다:
by_resolution '?group_level=2&start_key=["Di]',
'&end_key=["Dj"]', sep=""),
c('resolution', 'cat', 'count'))
by_resolution$count by_resolution$cat by_resolution$cat
ggplot(by_resolution, aes(cat, count, alpha=count)) +
scale_alpha(to=c(0.4, 0.9), legend=FALSE) +
coord_flip() +
geom_bar(fill='#333399′, stat='identity') +
labs(x=", y=") +
opts(title='검찰이 기소를 거부한 범죄') +
theme_bw()
R을 실행하면 다음과 같은 결과가 나옵니다:

다음 사항에 유의하세요. 절대 번호로 전화하세요. 기물 파손에 신경 쓸 뿐 폭행에는 관심이 없으니 SF에 전화하여 불만을 제기하지 마세요. 그런 경우가 더 많을 뿐입니다. 카테고리별로 해결 유형을 평가하고 이에 대해 어떻게 생각할지 결정하는 것은 독자의 몫으로 남겨두겠습니다.
결론
물론 며칠 동안 이 작업을 계속할 수도 있지만, 사람들이 제 프로세스를 이해하는 데 도움이 되고 싶었습니다. 제가 이 방법을 사용하는 대부분의 곳에서는 패턴이 비슷합니다. 데이터 집합은 매우 커질 수 있지만 집계는 작게 유지됩니다. 뷰를 점진적으로 처리하면 정렬 및 집계된 답변이 계속 빠르게 도착하고 처리 비용이 저렴하게 유지됩니다.
꽤 흥미로워 보이지만 코드 스니펫을 정리하면 훨씬 더 읽기 쉬울 것입니다!
https://dustin.github.com/2012/...
신고해 주셔서 감사합니다. 전송 중에 약간 먹혔습니다. 이상하게도 편집 화면에는 코드가 정상적으로 표시되었지만 게시할 때는 모든 것이 먹혔습니다.
저는 프로그래머는 아니지만 멋진 보고서가 있다고 말씀드리고 싶네요 :)
좋은 글입니다. 도트 플롯과 포커스된 하위 집합 차트 간에 데이터가 일치하지 않는 것 같습니다. 예를 들어, 점형 차트에서는 매춘 체포가 일요일, 화요일, 금요일에 가장 많이 발생하지만 초점 하위 집합 차트에서는 화요일, 수요일, 목요일에 가장 많이 발생하고 있습니다.
모든 R 함수와 패키지에 대해 투명하게 작동하나요? 너무 많은 데이터를 처리하면 메모리 부족이 발생하나요? 예를 들어 50GB의 데이터에 대해 최소자승 회귀 분석 또는 분산 분석(ANOVA)을 수행하려고 한다고 가정해 보겠습니다.