Nick Mihailovski, equipe da API Google Analytics – outubro de 2009
Este artigo discute como detectar e preencher valores de série temporal ausentes nos dados retornados da API de exportação de dados do Google Analytics.
Antes de começar
O artigo pressupõe que você conhece o funcionamento da API de exportação de dados do Google Analytics. O código de exemplo está em Java, mas você pode usar os conceitos na linguagem de sua escolha. O código deste artigo é fornecido como código aberto e pode ser obtido por download a partir da hospedagem do projeto.
Após ler este artigo, você terá aprendido:
- Como a API de exportação de dados do Google Analytics trata as dimensões de data.
- Como estruturar suas consultas para agrupar resultados e detectar datas ausentes.
- Como preencher os valores ausentes usando Java.
Introdução
A comparação de dados ao longo de um período fornece contexto.
Por exemplo, constatar que um website gerou uma receita de US$ 1 milhões não significa muita coisa. Porém, constatar que um website aumentou sua receita em 10 vezes de um trimestre a outro ou de um ano a outro é realmente impressionante. Com a API Google Analytics, é fácil exibir dados ao longo do tempo usando as dimensões ga:date
, ga:day
e ga:month
.
Se sua consulta usar somente uma dimensão de dados e se em algum dia do período não forem coletados dados, a Google Analytics API preencherá as datas e os valores 0
para as métricas.
ga:date | ga:sessions |
---|---|
2010-03-01 | 101 |
2010-03-02 | 0 |
2010-03-03 | 69 |
No entanto, pode ser complicado se você consultar uma data juntamente com outras dimensões. Se uma das datas não tiver dados, a API NÃO retornará uma entrada para essa data. Ela passará para a próxima data disponível que contenha dados.
ga:keyword | ga:date | ga:sessions |
---|---|---|
cadeira | 2010-03-01 | 55 |
cadeira | 2010-03-03 | 48 |
Idealmente, os analistas gostariam que as datas ausentes de uma determinada palavra-chave fossem preenchidas como no exemplo acima.
Este artigo descreve algumas das práticas recomendadas para preencher os dados de maneira pragmática.
Contexto
Primeiro, vamos analisar a causa do problema. Há duas razões.
- O Google Analytics processa somente os dados coletados. Se ninguém visitar o site em um determinado dia, não haverá dados a serem processados, logo, nenhum dado será retornado.
- É muito difícil determinar quantas dimensões adicionais e quais valores devem ser usados para datas que não possuem dados.
Assim, em vez de tentar definir um processo para regularizar tudo, a Google Analytics API deixa para o desenvolvedor o trabalho de preenchimento dos dados das consultas que têm várias dimensões. Sorte sua :)
Informações gerais do programa
Siga estas etapas para preencher os dados no gráfico acima.
- Modifique a consulta para garantir que as dimensões sejam classificadas de maneira adequada.
- Determine as datas esperadas do período.
- Repita e preencha os dados ausentes.
- Preencha qualquer valor ausente restante.
Modificação da consulta
Para preencher dados, é necessário verificar se os dados retornados pela API estão em um formato que facilita a detecção de datas ausentes.
Veja um exemplo de consulta para recuperar ga:keyword
e ga:date
nos primeiros cinco dias de março:
DataQuery dataQuery = new DataQuery(new URL(BASE_URL)); dataQuery.setIds(TABLE_ID); dataQuery.setStartDate("2010-03-01"); dataQuery.setEndDate("2010-03-05"); dataQuery.setDimensions("ga:keyword,ga:date"); dataQuery.setMetrics("ga:entrances");
Depois que a consulta é enviada para a API, os resultados contêm uma lista de objetos DataEntry
. Cada objeto de entrada representa uma linha de dados e inclui nomes e valores de dimensões/métricas. Como nenhum parâmetro de classificação foi usado, os resultados são retornados em ordem arbitrária.
ga:keyword | ga:date | ga:entrances |
---|---|---|
cadeira | 2010-03-04 | 14 |
cadeira | 2010-03-01 | 23 |
table | 2010-03-04 | 18 |
table | 2010-03-02 | 24 |
cadeira | 2010-03-03 | 13 |
Para identificar de maneira fácil as datas ausentes, primeiro precisamos agrupar todas as dimensões. Isso pode ser feito configurando o parâmetro de classificação da consulta como as dimensões usadas na consulta original.
dataQuery.setSort("ga:keyword,ga:date");
A adição do parâmetro de classificação faz com que a API retorne os resultados na ordem desejada.
ga:keyword | ga:date | ga:entrances |
---|---|---|
cadeira | 2010-03-01 | 23 |
cadeira | 2010-03-03 | 13 |
cadeira | 2010-03-04 | 14 |
table | 2010-03-02 | 24 |
table | 2010-03-04 | 18 |
A segunda etapa consiste em verificar se todas as datas são retornadas em ordem crescente para cada dimensão. Embora a API Google Analytics forneça várias dimensões de data, somente ga:date
pode ser classificado com precisão ao longo dos limites da data (ou seja, dias, meses, anos). Portanto, se você quiser preencher datas, verifique se a consulta usa a dimensão ga:date
nas dimensões e nos parâmetros de classificação da consulta.
Quando a consulta classificada for executada, todas as páginas de destino semelhantes serão retornadas próximas umas das outras, e as datas estarão em ordem sequencial. A lista das datas para uma única página de destino pode ser definida como uma série temporal e, como as datas ficam em ordem, é mais fácil identificar as datas ausentes.
Determinação das datas esperadas
Para determinar as datas ausentes, é necessário comparar as datas atuais retornadas pela API com as datas esperadas em cada série temporal. Podemos descobrir o que é esperado por meio de:
- Determinação das datas de início esperadas a partir da consulta de API.
- Contagem do número de dias esperados no período da consulta.
Os dois valores podem ser usados juntos para determinar cada data esperada aumentando a data de início em 1 unidade para cada dia no período.
Determinação da data de início esperada
Podemos usar o parâmetro de consulta start-date
como a data de início esperada da série. Como o formato de data retornado na resposta da API yyyyMMdd
é diferente do formato do parâmetro de consulta yyyy-MM-dd
, é necessário primeiro converter o formato de data antes de usá-lo.
O método setExpectedStartDate
converte os formatos das datas.
private static SimpleDateFormat queryDateFormat = new SimpleDateFormat("yyyy-MM-dd"); private static SimpleDateFormat resultDateFormat = new SimpleDateFormat("yyyyMMdd"); public void setExpectedStartDate(String startDate) { try { calendar.setTime(queryDateFormat.parse(startDate)); expectedStartDate = resultDateFormat.format(calendar.getTime()); } catch (ParseException e) { handleException(e); } }
Contagem do número de dias esperados
Para conferir o número de dias no período, o programa analisa as datas de início e
término em objetos Date
do Java. Em seguida, usa um objeto Calendar
para descobrir o tempo entre as duas datas. Um dia é adicionado à diferença entre as datas para que a contagem seja inclusiva.
private static final long millisInDay = 24 * 60 * 60 * 1000; public void setNumberOfDays(DataQuery dataQuery) { long startDay = 0; long endDay = 0; try { calendar.setTime(queryDateFormat.parse(dataQuery.getStartDate())); startDay = calendar.getTimeInMillis() / millisInDay; calendar.setTime(queryDateFormat.parse(dataQuery.getEndDate())); endDay = calendar.getTimeInMillis() / millisInDay; } catch (ParseException e) { handleException(e); } numberOfDays = (int) (endDay - startDay + 1); }
Agora temos todos os dados necessários para descobrir quais datas estão ausentes.
Identificação de cada série temporal nos resultados
Quando a consulta é executada, o programa passa por cada objeto DataEntry
na resposta da API. Como a consulta foi inicialmente classificada, a resposta terá uma série temporal parcial para cada palavra-chave. Assim, é necessário encontrar o início de cada série temporal e, em seguida, percorrer cada data e preencher os dados ausentes que a API não retornou.
Esse programa usa as variáveis dimensionValue
e tmpDimensionValue
para detectar o início de cada série.
Veja a seguir um código inteiro para lidar com a resposta. A maneira de preencher os dados ausentes é discutida abaixo.
public void printBackfilledResults(DataFeed dataFeed) { String expectedDate = ""; String dimensionValue = ""; List<Integer> row = null; for (DataEntry entry : dataFeed.getEntries()) { String tmpDimValue = entry.getDimensions().get(0).getValue(); // Detect beginning of a series. if (!tmpDimValue.equals(dimensionValue)) { if (row != null) { forwardFillRow(row); printRow(dimensionValue, row); } // Create a new row. row = new ArrayList<Integer>(numberOfDays); dimensionValue = tmpDimValue; expectedDate = expectedStartDate; } // Backfill row. String foundDate = entry.getDimension("ga:date").getValue(); if (!foundDate.equals(expectedDate)) { backFillRow(expectedDate, foundDate, row); } // Handle the data. Metric metric = entry.getMetrics().get(0); row.add(new Integer(metric.getValue())); expectedDate = getNextDate(foundDate); } // Handle the last row. if (row != null) { forwardFillRow(row); printRow(dimensionValue, row); } }
Preenchimento de qualquer data ausente
Para cada entrada de uma série, o programa armazena os valores de métrica (entradas) em um ArrayList
chamado row
. Quando uma nova série temporal é detectada, uma nova linha é criada, e a data esperada é definida como a data de início esperada.
Em seguida, para cada entrada, o programa verifica se o valor da data na entrada é equivalente à data esperada. Se forem equivalentes, a métrica na entrada será adicionada à linha. Do contrário, o programa terá detectado datas ausentes que precisam ser preenchidas.
O método backfillRow
processa o preenchimento de dados. Ele aceita como parâmetros as datas encontradas e esperadas, assim como a linha atual.
Em seguida ele determina o número de dias entre as duas datas (não inclusivos) e adiciona esse número de zeros à linha.
public void backFillRow(String startDate, String endDate, List<Integer> row) { long d1 = 0; long d2 = 0; try { calendar.setTime(resultDateFormat.parse(startDate)); d1 = calendar.getTimeInMillis() / millisInDay; calendar.setTime(resultDateFormat.parse(endDate)); d2 = calendar.getTimeInMillis() / millisInDay; } catch (ParseException e) { handleException(e); } long differenceInDays = d2 - d1; if (differenceInDays > 0) { for (int i = 0; i < differenceInDays; i++) { row.add(0); } } }
Quando o método termina, a linha terá sido preenchida com dados, e os dados atuais poderão ser adicionados. A data esperada é incrementada para um dia após
a data encontrada usando o método getNextDate
.
public String getNextDate(String initialDate) { try { calendar.setTime(resultDateFormat.parse(initialDate)); calendar.add(Calendar.DATE, 1); return resultDateFormat.format(calendar.getTime()); } catch (ParseException e) { handleException(e); } return ""; }
Preenchimento de qualquer valor restante
Depois que os dados da série forem processados em um row
, será necessário verificar se não há mais datas ausentes no final da série.
O método forwardFillRow
simplesmente calcula a diferença entre o
número de dias na consulta original e o tamanho atual da linha e adiciona esses
zeros ao final da linha.
public void forwardFillRow(List<Integer> row) { int remainingElements = numberOfDays - row.size(); if (remainingElements > 0) { for (int i = 0; i < remainingElements; i++) { row.add(0); } } }
Nesse momento, o programa terá preenchido qualquer valor ausente na série temporal. Agora que temos todos os dados, o programa salva os valores de dimensão e métrica como uma lista separada por vírgulas.
Conclusão
Usando essa amostra, você pode preencher facilmente dados em datas não retornadas pela API. Como mencionado acima, essa solução pode ser adaptada a qualquer linguagem de programação. Os desenvolvedores podem até mesmo adaptar essas técnicas e aplicá-las para lidar com várias dimensões e métricas. Agora ficou mais fácil do que nunca realizar análises avançadas em séries temporais retornadas pela Google Analytics API.