Google Refine + Perl (English)
Google Refine is awesome. If you're unaware of what it is, access their official page and watch at least the first screencast. You'll see it can be helpful for several ETL-related tasks.
Currently, I use it a lot, specially for simple (but boring) tasks, like loading a CSV, trimming out some outliers and saving as JSON to be imported into MongoDB. Nothing a Perl one-liner couldn't do.
However, the opposite is not true: Perl one-liners are a lot more flexible than Google Refine. Now, what if we could merge both?
- Google Refine could be easily integrated with any RESTful API.
- Perl transforms one-liners into RESTful webservices.
- PROFIT!!!
As a practical example, I'll use some georeferenced data I was working at. Let's suppose I have to deduplicate registers, and one of "duplicate" rules is their proximity on the map. Google Refine is far from a full-featured GIS, and is unable to handle bidimensional coordinate system. Enter the GeoDNA: an algorithm to lower geospatial dimensions. As it's FAQ says,
GeoDNA is a way to represent a latitude/longitude coordinate pair as a string. That sounds simple enough, but it's a special string format: the longer it is, the more accurate it is. More importantly, each string uniquely defines a region of the earth's surface, so in general, GeoDNA codes with similar prefixes are located near each other. This can be used to perform proximity searching using only string comparisons (like the SQL "LIKE" operator).
Another interesting property of GeoDNA is that when ordening a set of records by their GeoDNA code, close locations are likely to appear in adjacent rows (sometimes, close locations will share very different prefixes, but similar prefixes always represent close locations).
To incorporate GeoDNA into Google Refine, we'll use the Add column by fetching URLs option, clicking on the header of any column (which column it will be doesn't matter as we'll use two of them, anyway):
As the expression, we'll paste the following code (here, pay attention to the correct latitude/longitude column names):
'http://127.0.0.1:3000/?lat='+ row.cells['latitude'].value +'&lon='+ row.cells['longitude'].value |
Throttle delay can be zeroed, as our webservice is local. The final configuration should look like this (don't push the OK button, yet):
Now, check if you have Mojolicious and Geo::DNA Perl modules (install them via CPAN, if not) and paste into your terminal:
perl -MGeo::DNA -Mojo -E 'a("/"=>sub{my$s=shift;$s->render(json=>{geocode=>Geo::DNA::encode_geo_dna($s->param("lat"),$s->param("lon"))})})->start' daemon |
If you prefer a "human-readable" version, paste the following code into geocode-webservice.pl:
#!/usr/bin/env perl use Geo::DNA qw(encode_geo_dna); use Mojolicious::Lite; any '/' => sub { my $self = shift; $self->render(json => { geocode => encode_geo_dna( $self->param('lat'), $self->param('lon'), ), }); }; app->start; |
Once you started a webservice, it will report Server available at http://127.0.0.1:3000. Now, click OK on Google Refine dialog and wait. Even without delay, it could be a bit slow; however, even then this hack saved me a lot of time 😉
Google Refine + Perl
Google Refine é show. Para quem não conhece, acesse já o site oficial e assista pelo menos o primeiro vídeo. Serve para inúmeras tarefas envolvendo ETL.
Eu, no momento, uso muito para coisas simples, como carregar um CSV, eliminar outliers e salvar em JSON para carregar no MongoDB. Nada que um one-liner em Perl não pudesse fazer.
Infelizmente, a recíproca não é válida: one-liners em Perl são bem mais versáteis do que o Google Refine. Que tal integrar os dois?
- Google Refine pode ser facilmente integrado com um web-service qualquer.
- Perl transforma one-liners em webservices.
- PROFIT!!!
Como exemplo concreto, utilizarei dados georeferenciados. Digamos que preciso eliminar registros duplicatas, e uma das formas de detectar é pela proximidade física dos pontos no mapa. Google Refine não é um GIS, e não faz a menor ideia de como processar latitude/longitude. Entra o GeoDNA: um algoritmo que transforma coordenadas numéricas bidimensionais em uma string com propriedade interessante: quanto mais longo o prefixo compartilhado de dois GeoDNAs, mais perto estão as respectivas coordenadas no mapa (infelizmente, GeoDNAs com prefixos totalmente distintos também podem estar fisicamente próximos; localidades próximas ao meridiano de Greenwich são um exemplo clássico). Portanto, ordenando os registros por GeoDNA, as localidades próximas tenderão a ficar em linhas adjascentes. Para a integração, usaremos o recurso Add column by fetching URLs, clickando em header de qualquer coluna (a coluna não importa pois, de qualquer maneira, usaremos dados de duas):
No diálogo que aparecer, colamos o seguinte código (atenção para o nome das colunas com as respectivas coordenadas):
'http://127.0.0.1:3000/?lat='+ row.cells['latitude'].value +'&lon='+ row.cells['longitude'].value |
Não precisamos do throttle delay, pois o nosso webservice é local. O diálogo deve ficar assim (nada de clickar em OK, ainda):
Agora, certifique-se de que você tem os módulos Mojolicious e Geo::DNA instalados, e rode no terminal:
perl -MGeo::DNA -Mojo -E 'a("/"=>sub{my$s=shift;$s->render(json=>{geocode=>Geo::DNA::encode_geo_dna($s->param("lat"),$s->param("lon"))})})->start' daemon |
Se preferir, aqui está a versão "por extenso":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/bin/env perl use Geo::DNA qw(encode_geo_dna); use Mojolicious::Lite; any '/' => sub { my $self = shift; $self->render(json => { geocode => encode_geo_dna( $self->param('lat'), $self->param('lon'), ), }); }; app->start; |
Uma vez iniciado o webservice, clique em OK lá no diálogo do Google Refine e aguarde. Mesmo sem o delay, é relativamente lento; porém o custo/benefício dessa gambiarrinha é, evidentemente, favorável 😉
DBIx::Class HATE

Exercendo a minha liberdade de expressão, vou tentar explicar por que raios, afinal, eu detesto tanto o DBIx::Class. Ninguém é obrigado a concordar comigo, mas espero que os que leiam, reflitam.
Sem dúvida, é um treco robusto e consistente, mas já dizia o Oscar Wilde: "Consistency is the last refuge of the unimaginative." Agora eu, felizmente (ou não), tenho imaginação de sobra. Sou conhecido por minhas gambiarras atrozes. Sou um péssimo programador, a ponto de ter largado a faculdade de computação no quarto ano e ter me formado em desenho industrial 
O que gosto no Perl é a sua capacidade de ser um master-glue, amarrando pedaços desconexos numa "coisa" que, magicamente, atende às necessidades.
Como bem ilustra o fabuloso poema "Black Perl", o Perl é livre de preconceitos e purismos. Posso "furar" a privacidade dos atributos do Moose, subverter o MVC com o Mojolicious, além de fazer todo tipo de monkeypatching bizarro e ad-hoc.
Aí entra o DBIx::Class. Seu glorioso dbicdump já ofende os meus olhos:
# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-08-24 13:26:20 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:qqyhu9B0Vr7l1KsQ6CG7Jw
Desgraçado, como assim?! Em primeiro lugar, a caralhada de classes (uma por tabela?!), para mim, por si só, é uma coisa javesca. E, em segundo lugar, o mentecapto é prolixo e nem posso ousar de intervir com o Perl::Tidy para deixar o código legível do jeito que gosto. Sem falar que o schema é "lossy" e perde várias informações.
Mas isso é o de menos. Não consigo imaginar um workflow que me agrade. Eu gosto do pgAdmin e do HeidiSQL e costumo elaborar as minhas tabelas e queries nessas GUIs. Isso me dá um retrabalho na hora de "portar" para Perl (diferentemente do DBI, aonde é só copiar e colar, ráááá).
E olha só, CRUD não é o foco do meu trabalho. Às vezes, preciso dar uma cutucada no banco de dados, ou então um grande processamento em lote. Mas variedade de queries? Não me pertence.
Aliás, um ponto importante: em geral, sou "equipe de um homem só". E Perl dá um grande poder nas mãos das equipes de um homem só. Não preciso de um código de fácil manutenção. Eu assumo a penalidade e o risco de me perder nas próprias queries quilométricas. Me divirto muito com isso.
Nesse ponto, DBIx::Class me parece coisa de tiozão. Me lembra aqueles emails corporativos aonde a assinatura é 100x maior do que a mensagem, sendo o schema a assinatura e o uso da mesma, a mensagem 

