Заполнение пропущенных значений из запросов даты

Ник Михайловски, команда Google Analytics API – октябрь 2009 г.

В этой статье рассказывается, как обнаружить и заполнить недостающие значения временных рядов в данных, возвращаемых из API экспорта данных Google Analytics.


Прежде чем вы начнете

В статье предполагается, что вы знаете, как работает API экспорта данных Google Analytics. Пример кода написан на Java, но вы можете использовать концепции на выбранном вами языке. Код для этой статьи предоставлен с открытым исходным кодом и его можно скачать с хостинга проекта .

Прочитав эту статью, вы узнаете:

  • Как API экспорта данных Google Analytics обрабатывает измерения даты.
  • Как структурировать запросы, чтобы сгруппировать результаты и обнаружить пропущенные даты.
  • Как заполнить пропущенные значения с помощью Java.

Введение

Сравнение данных за определенный период времени обеспечивает контекст. Например, утверждение, что веб-сайт принес доход в 1 миллион долларов, не имеет большого значения. Но заявление о том, что веб-сайт увеличил доход в 10 раз за квартал по сравнению с кварталом или годом по сравнению с прошлым годом, действительно впечатляет. С помощью API Google Analytics можно легко отображать данные с течением времени, используя измерения ga:date , ga:day и ga:month .

Если в вашем запросе используется только измерение даты и в какие-либо дни в диапазоне дат не собрано нулевых данных, Google Analytics API заполнит даты и 0 значения для показателей.

га:дата га:сессии
01.03.2010 101
2010-03-02 0
03.03.2010 69

Однако это становится сложнее, если вы запрашиваете дату вместе с другими измерениями. Если для одной из дат нет данных, API НЕ вернет запись для этой даты. Он просто перейдет к следующей доступной дате, содержащей данные.

га:ключевое слово га:дата га:сессии
стул 01.03.2010 55
стул 03.03.2010 48

В идеале аналитики хотели бы, чтобы недостающие даты для определенного ключевого слова были заполнены, как в первом примере выше.

В этой статье описаны некоторые лучшие практики прагматичного обратного заполнения данных.

Фон

Давайте сначала посмотрим, почему существует эта проблема. Есть 2 причины.

  1. Google Analytics обрабатывает только собранные данные. Если в определенный день на сайт никто не заходил, то данных для обработки нет, поэтому данные не возвращаются.
  2. Очень сложно определить, сколько дополнительных измерений и какие значения следует использовать для дат, по которым нет данных.

Таким образом, вместо того, чтобы пытаться определить один процесс, который будет управлять ими всеми, API Google Analytics оставляет разработчику задачу по заполнению данных для запросов, имеющих несколько измерений. Повезло тебе :)

обзор программы

Ниже приведены шаги по заполнению данных на диаграмме выше.

  1. Измените запрос, чтобы обеспечить удобную сортировку измерений.
  2. Определите ожидаемые даты из диапазона дат.
  3. Повторите и заполните все недостающие даты.
  4. Заполните оставшиеся пропущенные значения.

Изменить запрос

Для обратного заполнения дат нам необходимо убедиться, что данные, возвращаемые из API, имеют формат, позволяющий легко обнаружить отсутствие даты. Вот пример запроса для получения ga:keyword и ga:date за первые 5 дней марта:

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");

После отправки запроса в API результаты будут содержать список объектов DataEntry . Каждый объект ввода представляет собой строку данных и включает имена и значения для параметров/показателей. Поскольку параметр сортировки не использовался, результаты возвращаются в произвольном порядке.

га:ключевое слово га:дата га: входы
стул 04.03.2010 14
стул 01.03.2010 23
стол 04.03.2010 18
стол 2010-03-02 24
стул 03.03.2010 13

Чтобы можно было легко определить, какие даты отсутствуют, нам нужно сначала сгруппировать все измерения вместе. Это можно сделать, установив для параметра сортировки запроса размеры, использованные в исходном запросе.

dataQuery.setSort("ga:keyword,ga:date");

Добавление параметра сортировки заставит API возвращать результаты в желаемом порядке.

га:ключевое слово га:дата га: входы
стул 01.03.2010 23
стул 03.03.2010 13
стул 04.03.2010 14
стол 2010-03-02 24
стол 04.03.2010 18

Второй шаг — убедиться, что для каждого измерения все даты возвращаются в порядке возрастания. Хотя Google Analytics API предоставляет несколько измерений даты, только ga:date может быть точно отсортирован по границам дат (т. е. по дням, месяцам, годам). Поэтому, если вы хотите заполнить даты, убедитесь, что ваш запрос использует измерение ga:date как в измерениях, так и в параметрах запроса сортировки.

После выполнения отсортированного запроса все одинаковые целевые страницы будут возвращены рядом друг с другом, а даты будут расположены в последовательном порядке. Список дат для одной целевой страницы можно рассматривать как временной ряд, и поскольку они расположены по порядку, гораздо легче выявить недостающие даты.

Определите ожидаемые даты

Чтобы обнаружить пропущенные даты, нам нужно сравнить фактические даты, возвращаемые API, с ожидаемыми датами в каждом временном ряду. Мы можем выяснить, что ожидается:

  1. Определение ожидаемой даты начала по запросу API.
  2. Подсчет количества ожидаемых дней в диапазоне дат запроса.

Оба значения можно использовать вместе для определения каждой ожидаемой даты путем увеличения даты начала на 1 для каждого дня в диапазоне дат.

Определение ожидаемой даты начала

Мы можем использовать параметр запроса start-date в качестве ожидаемой даты начала серии. Поскольку формат даты, возвращаемый в ответе API yyyyMMdd отличается от формата параметра запроса yyyy-MM-dd , нам необходимо сначала преобразовать формат даты, прежде чем мы сможем его использовать.

Метод setExpectedStartDate преобразует форматы дат.

  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);
    }
  }

Подсчет количества ожидаемых дней

Чтобы получить количество дней в диапазоне дат, программа анализирует даты начала и окончания в объекты Java Date . Затем использует объект Calendar , чтобы определить время между обеими датами. К разнице дат добавляется один день, чтобы сделать счет инклюзивным.

  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);
  }

Теперь у нас есть все данные, необходимые для того, чтобы выяснить, какие даты отсутствуют.

Определите каждый временной ряд в результатах

После выполнения запроса программа обрабатывает каждый объект DataEntry в ответе API. Поскольку запрос изначально был отсортирован, ответ будет содержать частичный временной ряд для каждого ключевого слова. Поэтому нам нужно найти начало каждого временного ряда, затем просмотреть каждую дату и заполнить недостающие данные, не возвращенные API.

Эта программа использует переменные dimensionValue и tmpDimensionValue для определения начала каждой серии.

Вот весь код для обработки ответа. Заполнение недостающих данных рассмотрено ниже.

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);
  }
}

Заполните недостающие даты

Для каждой записи в серии программа сохраняет значения метрик (входы) в ArrayList , называемый row . При обнаружении нового временного ряда создается новая строка, а ожидаемая дата устанавливается на ожидаемую дату начала.

Затем для каждой записи программа проверяет, соответствует ли значение даты в записи ожидаемой дате. Если они равны, метрика из записи добавляется в строку. В противном случае программа обнаружила пропущенные даты, которые необходимо заполнить заново.

Метод backfillRow обрабатывает данные обратного заполнения. Он принимает в качестве параметров ожидаемые и найденные даты, а также текущую строку. Затем он определяет количество дней между двумя датами (не включительно) и добавляет в строку необходимое количество нулей.

  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);
      }
    }
  }

Когда метод будет выполнен, строка будет заполнена данными, и можно будет добавить текущие данные. Затем ожидаемая дата увеличивается на один день после найденной даты с помощью метода 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 "";
}

Заполните оставшиеся значения

После того как данные серии были обработаны в row , мы должны убедиться, что в конце серии больше нет пропущенных дат.

Метод forwardFillRow просто вычисляет разницу между количеством дней в исходном запросе и текущим размером строки и добавляет необходимое количество нулей в конец строки.

public void forwardFillRow(List<Integer> row) {
  int remainingElements = numberOfDays - row.size();
  if (remainingElements > 0) {
    for (int i = 0; i < remainingElements; i++) {
      row.add(0);
    }
  }
}

На этом этапе программа заполнила все недостающие значения во временном ряду. Теперь, когда у нас есть все данные, программа печатает значения параметров и показателей в виде списка, разделенного запятыми.

Заключение

Используя этот пример, вы можете легко заполнить данные по датам, которые не возвращаются API. Как уже говорилось выше, это решение можно адаптировать под любой язык программирования. Разработчики могут даже адаптировать эти методы и применять их для обработки нескольких измерений и нескольких показателей. Теперь приступить к расширенному анализу временных рядов, возвращаемых API Google Analytics, стало еще проще, чем когда-либо.