Mapreduce incremental para análises com R

[Esta postagem também aparece no blog do Dustin no github].

Eu queria descrever um pouco do meu trabalho com o uso do R para me ajudar a entender os dados que estou coletando no Couchbase Server porque o considero bastante interessante, útil e fácil. Entretanto, tem sido difícil para mim descobrir um bom ponto de partida porque não sei quem seria o público-alvo. Ou seja, encontrar o conjunto certo de premissas para começar tem sido bastante difícil.

Na semana passada, no entanto, conversei com um cara realmente incrível de uma empresa de mídia que tinha uma pergunta específica: "Como meus analistas podem gerar relatórios sobre todos os dados maravilhosos que estou armazenando em Couchbase?" Fui mais a fundo. Quem são esses analistas? Que ferramentas eles usam?

as visualizações incrementais do Map Reduce são idênticas às visualizações do Apache CouchDB, portanto, tudo também funcionará com o CouchDB

Meu público

Acontece que os analistas estão bem próximos do que eu imaginava. Eles costumam usar algum tipo de ferramenta de data warehousing da Oracle que faz todo tipo de mágica e depois cai com força se você sair dos limites com os quais eles se sentem confortáveis. Isso parecia ser algo que eu poderia ignorar. Mas então ele disse algo que me deu uma base bastante sólida. Embora não sejam programadores, eles usam R como parte de sua análise de dados.

Como essa pergunta foi feita por um usuário do Couchbase que queria saber como extrair seus dados, vou presumir que quem estiver lendo isso conheça o R um pouco melhor do que o Couchbase.

Sobre as visualizações

Há muitas coisas que você pode ler se quiser entender o conceito de visualização de sofá. O capítulo de visualização do Manual do servidor Couchbase cobre o conceito muito bem. Se você quiser saber tudo o que é possível saber, então pesquise sobre o assunto, mas para a maioria dos meus usos, ele realmente se resume a três coisas:

  1. Extraia as informações úteis.
  2. Classifique, colocando coisas iguais juntas.
  3. Faça uma agregação básica.

É assim que eu pego muitos dados e os transformo em informações úteis na maioria das vezes. Espero que os exemplos a seguir ajudem você a fazer o mesmo.

Os dados

A parte mais difícil de qualquer tutorial de grokking de dados é que ele nunca é sobre seus dados. Isso o torna menos interessante para o leitor e, com frequência, dificulta um pouco a aplicação em seus próprios problemas.

Infelizmente, os dados mais interessantes que extraio regularmente para relatórios são um tanto confidenciais, por isso não posso compartilhar as coisas que mais utilizo, mas espero que isso o ajude a encontrar algo interessante.

Os dados com os quais escolhi trabalhar são o conjunto de incidentes relatados pelo SFPD do Site de dados de SF site da Web. É praticamente tudo o que a polícia de São Francisco relatou desde 2003.

Esses documentos são bastante regulares e simples. Seus dados podem ser mais complicados, mas as técnicas são as mesmas. Vamos começar examinando um exemplo de documento do conjunto de dados do SFPD:

{
"category" (categoria): "Prostituição",
"incident_id": 90096348,
"distrito": "Tenderloin",
"timestamp" (registro de data e hora): “2009-01-27T04:03:00”,
"lon": -122.416261836834,
"lat": 37.7853750846376,
"Localização": "Ofarrell St / Hyde St",
"time": "04:03",
"date": "2009-01-27",
"resolução": "Arrest, Cited",
"day": "Tuesday" (terça-feira),
"desc": "Solicita visita a casa de prostituição"
}

Acho que consigo entender o que são todas essas coisas, então vamos ao trabalho.

Obtendo dados no R

Há alguns pacotes que usarei aqui, portanto, vamos nos certificar de que eles estejam em seu R antes de começarmos:

install.packages(c('rjson', 'ggplot2', 'reshape'),
dependencies=TRUE)

require(rjson)
require(reshape)
require(RColorBrewer)
require(ggplot2)

Como o R gosta de dados "quadrados", costumo fazer com que a saída de minhas exibições seja muito regular, o que também significa que posso ter funções muito simples para pegar uma exibição e retirá-la. Para esse fim, tenho uma configuração básica comum em meus scripts do R que se parece com isso:

# Ponteiro para sua base de visualização do couchbase. É aqui que você encontra seu
Dados próprios do #
urlBase

# Essa é a sua solicitação GET básica -> JSON analisado.
getData fromJSON(file=paste(urlBase, subpath, sep="))$rows
}

# E isso o transforma em um quadro de dados, com a opção de nomear o
Colunas #.
getFlatData b if (!is.null(n)) {
names(b) }
b
}

# Além disso, vou trabalhar com dias da semana, por isso preciso disso:
dow 'Thursday', 'Friday', 'Saturday')
curto-circuito

Contagem geral de denúncias de crimes

Como acontece com a maioria dos conjuntos de dados, não sei nem por onde começar, então primeiro vamos ver que tipos de crimes temos. Estou interessado em contagens totais e contagens por dia da semana. O bom é que, com as exibições de sofá, posso criar uma única exibição que me dirá qualquer uma delas. Vamos dar uma olhada na fonte da exibição:

function(doc) {
emit([doc.category, doc.day], null);
}

Parece muito simples, mas combinado com o _countredutor incorporado, isso pode fazer muitas coisas legais ao agrupar. Com group_level=1obtemos a contagem de crimes por categoria. Vamos plotar isso e ver o que é popular. Supondo que salvamos isso em um documento de design chamado categoriascom o nome da visualização de por diaVeja o que você diz a R:

# Obter um quadro de dados contendo as categorias e suas respectivas contagens
cat c('cat', 'count'))

# As colunas retornam como strings e precisam de correções para se tornarem úteis
cat$cat cat$count # Além disso, achei que a classificação por contagem facilitou a compreensão
cat$cat

# Agora, trace o gráfico.
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="comma") +
labs(x=", y=") +
opts(title='Total Crime Reports') +
coord_flip() +
theme_bw()

Então, R lhe dará isso:

all cats

Por dia da semana

Achei isso um tanto interessante, então quis saber qual era a distribuição por dia da semana. Posso usar a mesma exibição acima com group_level=2Mas, como as taxas são muito diferentes, pedi ao R que calculasse a variação relativa em todo o quadro de dados para cada categoria por dia da semana e, em seguida, fiz o gráfico. Aqui está o código do R:

# Obtenha os mesmos dados, mas separados por dia da semana.
cat_byday c('cat', 'day', 'count'))

# Estou fazendo um reparo semelhante ao acima, mas com outro pedido e
# algumas visualizações do dia da semana (muito para brincar)
cat_byday$cat cat_byday$cat cat_byday$count cat_byday$cat_by_count cat_byday$day cat_byday$shortdow

# Calcule a porcentagem de cada categoria de acordo com o dia da semana
a1 a2 sum)
total_por_dia cat_byday$perc

# Vamos ver como isso se parece
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()

Isso parece muita configuração, mas a maior parte foi apenas configuração de tipo e outras coisas. Reutilizaremos algumas delas abaixo. No entanto, neste ponto, temos algo para dar uma olhada:

all cats by day

Subconjunto focalizado

Há muitos dados aqui e todos eles são relativos, o que torna difícil ver como comparar as coisas. Eu queria realmente examinar algumas áreas e descobrir que tipos de correlações existiam. Como eu já tinha os dados carregados, pensei em pegar um subconjunto do que já havia sido solicitado e fazer um gráfico de facetas.

# Escolha algumas categorias de interesse
interessante 'Prostituição',
"Embriaguez",
'Conduta desordeira')

# Extrair apenas esse subconjunto e refatorar as categorias
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="comma") +
labs(x=", y=") +
opts(title='Select Crime Reports by Day') +
theme_bw()

E isso deve me mostrar muito mais detalhes sobre essas categorias individuais.

sex and drugs

Pessoalmente, achei bastante interessante a falta de correlação entre incidentes relacionados ao álcool e outros. O álcool parece ser a antidroga. Talvez as prostitutas não gostem de bêbados. Quem sabe...

Ao longo do tempo

Nesse ponto, percebi que os dados remontam a 2003 e nem sequer considerei se as coisas estão melhorando ou piorando. Na verdade, não explorei muito esse assunto, mas queria ter uma ideia rápida se as coisas estão melhorando ou piorando. Aqui está uma exibição que nos informa as taxas de incidentes por ano e categoria:

function(doc) {
var ymd = doc.date.split('-');
emit([parseInt(ymd[0], 10), doc.category], null);
}

Como em todos esses exemplos, eu combino isso com o _countredução incorporada. Vamos apenas mapear as taxas anuais com o seguinte R:

byyear c('year', 'count'))
byyear$year

# Não há 2012 suficiente aqui, portanto, vamos ignorá-lo para este gráfico.
ggplot(byyear[byyear$year < 2012,], aes(year, count)) +
stat_smooth(fill=’#333399′) +
labs(y=", x=") +
scale_y_continuous(formatter=comma) +
opts(title="Total Incident Reports by Year") +
theme_bw()

O que isso nos diz?

by year

Parece que as coisas estão melhorando (ou a polícia está ficando mais preguiçosa). Eu poderia me aprofundar um pouco mais no assunto para descobrir se isso é verdade para todas as categorias, mas não estou muito interessado, então vamos dar uma olhada em outra coisa.

Crime por área

Eu estava interessado em saber se determinados crimes eram mais populares em algumas áreas do que em outras. Estou usando o documento distritopara isso (em vez das coordenadas incorporadas) e achei que poderia ser um bom caso de uso para um mapa de calor.

Uma coisa que notei é que alguns relatórios não têm um distrito associado a eles. Optei por ignorá-los para este relatório, mas você pode facilmente ver como substituí-los por um valor personalizado se quiser considerar isso especificamente. Vamos começar com o seguinte código de visualização:

function(doc) {
Se (doc.district != null) {
emit([doc.category, doc.district], null);
}
}

É claro que usaremos o _countincorporado novamente. Uma coisa que devo observar sobre isso é que, embora eu tenha planejado originalmente todos dados, mais tarde decidi que não estava interessado em nenhuma área que tivesse menos de 1.000 crimes registrados. Como esse é o saída do filtro, precisei aplicá-lo no R, pois não temos como solicitar isso em uma visualização (já que as visualizações são materializadas e a função de mapa não incluiu um filtro antes da aplicação da redução). O ideal seria oferecer suporte a isso na solicitação de exibição real, mas, enquanto isso, podemos extraí-lo facilmente na postagem:

by_region c('cat', 'region', 'count'))
by_region$count by_region$region

# Ignore qualquer coisa que não tenha pelo menos 1.000 incidentes
pop_regions 1000,]
pop_regions$cat # E fazer com que os crimes mais quentes cheguem ao topo
pop_regions$cat

ggplot(pop_regions, aes(x=region, y=cat, fill=count, alpha=count)) +
geom_tile() +
scale_fill_continuous('Incidents',
formatador=função(x)
sprintf("%dk", x / 1000)) +
scale_alpha_continuous(legend=FALSE, to=c(0.7, 1.0)) +
labs(x=", y=") +
theme_bw() +
opts(title='Tipos de crime por distrito',
axis.text.x=theme_text(angle=-90),
legend.position='right')

Isso nos dá o seguinte mapa de calor:

regions

As áreas em branco não registraram 1.000 incidentes do tipo de crime especificado na área indicada desde 2003. As áreas em azul claro tiveram alguns incidentes. As vermelhas brilhantes tiveram a maioria. Parece que eu quero evitar o distrito sul.

Quantos o promotor público recusou?

Como um exemplo de extração de um agregado do lado do servidor em parte dos dados, achei o tipo de resolução "District Attorney Refuses To Prosecute" (Promotor Público se recusa a processar) particularmente interessante, então quis saber com que frequência isso acontece. Novamente, começamos com uma exibição simples:

function(doc) {
emit([doc.resolution, doc.category], null);
}

Em seguida, fazemos nosso trabalho normal _countcoisa. No entanto, a diferença aqui é que, quando faço a solicitação, quero usar o chave_iniciale end_keypara encontrar apenas coisas que foram resolvidas dessa forma. Sei que a lista de resoluções vai de "District Attorney Refuses To Prosecute" a "Exceptional Clearance", portanto, posso simplesmente procurar coisas que comecem com "Di" e terminem com coisas que comecem com "Dj". Essas também são matrizes que estou emitindo, portanto, é realmente baseado no primeiro elemento da matriz. O código R tem a seguinte aparência:

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='Crimes que o promotor público se recusou a processar') +
theme_bw()

R nos dá o seguinte:

regions

Observe que eles são absoluto números. Não ligue para SF e reclame porque eles não se importam com agressões e sim com vandalismo. Simplesmente há mais casos desse tipo. Deixarei como exercício para o leitor avaliar os tipos de resolução por categoria e decidir o que pensar sobre eles.

Em conclusão

Obviamente, eu poderia continuar com isso por dias, mas só queria ajudar as pessoas a entender meu processo. Na maioria dos lugares em que uso isso, os padrões são semelhantes. Os conjuntos de dados podem se tornar muito grandes, mas as agregações permanecem pequenas. O processamento incremental das exibições significa que minhas respostas classificadas e agregadas continuam a chegar rapidamente e o processamento permanece barato.

Compartilhe este artigo
Receba atualizações do blog do Couchbase em sua caixa de entrada
Esse campo é obrigatório.

Autor

Postado por Dustin Sallings, arquiteto-chefe da Couchbase

Dustin Sallings é arquiteto-chefe da Couchbase. Dustin é autor do spymemcached e um dos principais colaboradores do Couchbase e do Projetos do Memcached.

6 Comentários

  1. Richard Stanford abril 16, 2012 em 8:51 pm

    Isso parece bastante interessante, mas seria muito mais fácil de ler se os trechos de código fossem limpos!

    1. Obrigado pelo relatório. O código foi um pouco corrompido no transporte. Estranhamente, a tela de edição mostrava o código sem problemas, mas quando o publicava, tudo era corrompido.

  2. Não sou programador, mas devo dizer que você tem alguns relatórios interessantes :)

  3. Belo artigo. Parece que os dados não coincidem entre seu gráfico de pontos e os gráficos de subconjuntos focados. Por exemplo, no gráfico de pontos, as prisões por prostituição são mais comuns no domingo, na terça e na sexta, mas em seu gráfico de subconjunto focalizado, essas prisões são mais comuns na terça, na quarta e na quinta.

  4. Ele funciona de forma transparente para todas as funções e pacotes do R? Ele produziria um problema de falta de memória se lidarmos com muitos dados? Por exemplo, imagine que eu queira executar uma regressão de mínimos quadrados ou um teste ANOVA de 50 GB de dados.

Deixe um comentário

Pronto para começar a usar o Couchbase Capella?

Iniciar a construção

Confira nosso portal do desenvolvedor para explorar o NoSQL, procurar recursos e começar a usar os tutoriais.

Use o Capella gratuitamente

Comece a trabalhar com o Couchbase em apenas alguns cliques. O Capella DBaaS é a maneira mais fácil e rápida de começar.

Entre em contato

Deseja saber mais sobre as ofertas do Couchbase? Deixe-nos ajudar.