Filling In Missing Values From Date Requests

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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 os 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:datega:sessions
2010-03-01101
2010-03-020
2010-03-0369

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:keywordga:datega:sessions
cadeira2010-03-0155
cadeira2010-03-0348

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.

  1. 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.
  2. É 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. Que sorte a sua! :)

Visão geral do programa

Siga estas etapas para preencher os dados no gráfico acima.

  1. Modifique a consulta para garantir que as dimensões sejam classificadas de maneira adequada.
  2. Determine as datas esperadas do período.
  3. Repita e preencha os dados ausentes.
  4. 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:keywordga:datega:entrances
cadeira2010-03-0414
cadeira2010-03-0123
tabela2010-03-0418
tabela2010-03-0224
cadeira2010-03-0313

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:keywordga:datega:entrances
cadeira2010-03-0123
cadeira2010-03-0313
cadeira2010-03-0414
tabela2010-03-0224
tabela2010-03-0418

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, apenas ga:date pode ser classificado com precisão ao longo dos limites de data (ou seja, dia, mês ou ano). Portanto, se você quiser preencher as datas, verifique se a consulta usa a dimensão ga:date nas dimensões e classifica os parâmetros de 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:

  1. Determinação das datas de início esperadas a partir da consulta de API.
  2. 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 da data retornado na resposta de API yyyyMMdd é diferente do formato do parâmetro de consulta yyyy-MM-dd, primeiro precisamos converter o formato da 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 saber 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.

Este 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 em 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 são processados em um row, precisamos verificar se não há mais datas ausentes no final da série.

O método forwardFillRow apenas calcula a diferença entre o número de dias da consulta original e o tamanho atual da linha, adicionando esse número de 0 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 os dados nas datas que não foram 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.