填入日期要求中遺漏的值

Google Analytics (分析) API 團隊 Nick Mihailovski - 2009 年 10 月

本文說明如何偵測及補充 Google Analytics (分析) Data Export API 傳回的資料中缺少的時間序列值。


事前準備

本文假設您瞭解 Google Analytics (分析) 資料匯出 API 的運作方式。程式碼範例是以 Java 編寫,但您可以使用所選語言中的概念。本文的程式碼是以開放原始碼形式提供,並 從專案代管位置下載

閱讀本文後,您將會瞭解:

  • Google Analytics (分析) 資料匯出 API 如何處理日期維度。
  • 如何建立查詢結構,將結果分組及偵測遺漏的日期。
  • 如何使用 Java 填入遺漏的值。

簡介

比較一段時間內的資料可提供背景資訊。舉例來說,假設網站收益創造了 $100 萬美元的收益,並沒有任何意義。不過,假如網站收益逐季或年增率提高 10 倍,確實的成效確實相當出色。有了 Google Analytics API,您可以輕鬆使用 ga:datega:dayga:month 維度繪製長期資料。

如果查詢只使用日期維度,如果日期範圍內有任何日期收集零資料,Google Analytics (分析) API 就會補充指標的日期和 0 值。

ga:datega:sessions
2010-03-01101
2010-03-020
2010-03-0369

不過,如果您查詢日期和其他維度,資料就會變得棘手。如果其中一個日期沒有資料,API 就「不會」傳回該日期的項目。只會跳到下一個含有資料的可用日期。

ga:keywordga:datega:sessions
椅子2010-03-0155
椅子2010-03-0348

在理想的情況下,分析人員會希望特定關鍵字缺少日期填入 (如上方第一個範例所示)

本文說明實際補充資料的最佳做法。

背景

首先,我們來瞭解一下這個問題的發生原因。原因有 2 個:

  1. Google Analytics (分析) 只會處理收集到的資料。只要沒有人在特定日期造訪網站,就不會有可處理的資料,因此也不會傳回任何資料。
  2. 很難判斷在沒有資料的日期裡應使用多少額外的維度,以及該使用哪些值。

因此,Google Analytics (分析) API 不會對開發人員來說,為包含多個維度的查詢填入資料,而非嘗試定義一個程序來全部管理。Lucky You :)

計畫總覽

請按照下列步驟補充上圖中的資料。

  1. 修改查詢,確保維度按機會排序。
  2. 從日期範圍內決定預期日期。
  3. 反覆疊代並補充所有遺漏的日期。
  4. 填入所有遺漏的值。

修改查詢

如要補充日期,我們必須確保 API 傳回的資料採用易於偵測的日期格式。以下查詢範例說明如何同時擷取 3 月前 5 天的 ga:keywordga:date

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 物件清單。每個項目物件都代表一列資料,其中包含維度/指標的名稱和值。由於未使用排序參數,因此結果會以任意順序傳回。

ga:keywordga:datega:entrances
椅子2010-03-0414
椅子2010-03-0123
資料表2010-03-0418
資料表2010-03-0224
椅子2010-03-0313

為了輕鬆找出遺漏的日期,您必須先將所有維度分組。方法是將查詢的排序參數設為原始查詢中使用的維度。

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

新增排序參數會讓 API 以所需順序傳回結果。

ga:keywordga:datega:entrances
椅子2010-03-0123
椅子2010-03-0313
椅子2010-03-0414
資料表2010-03-0224
資料表2010-03-0418

第二步是確保每個維度都會以遞增順序傳回所有日期。雖然 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);
  }

現在,我們已取得所有資料,因此無法判斷遺漏的日期。

找出結果中的各個時間序列

執行查詢後,程式會經歷 API 回應中的每個 DataEntry 物件。由於查詢最初是排序,因此每個關鍵字的回應都有部分時間序列。因此,我們需要找出每個時間序列的開始時間,然後逐一查看每個日期,並填入 API 未傳回的資料。

此程式會使用 dimensionValuetmpDimensionValue 變數來偵測每個序列的開頭。

以下是用來處理回應的完整程式碼。以下將說明如何填寫遺漏的資料。

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

補回任何遺漏的日期

對於系列中的每個項目,程式會將指標值 (進入) 儲存在名為 rowArrayList 中。偵測到新的時間序列時,系統會建立新的資料列,並將預期日期設為預期的開始日期。

然後,程式會檢查每個項目的日期值是否等於預期日期。如果兩者相等,系統會將項目中的指標新增至資料列。否則,程式偵測到遺漏需要補充的日期。

backfillRow 方法會處理補充資料。可接受當做預期、找到日期和目前資料列的參數。接著判斷兩個日期之間的天數 (不含頭尾),並將數字 0 加到資料列中。

  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 方法只會計算原始查詢中天數與目前資料列大小的差距,然後將這麼多 0 秒相加至資料列末端。

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 未傳回的日期資料。如前所述,這項解決方案可以適應任何程式設計語言。開發人員甚至可以調整這些技巧,並應用這些技術來處理多個維度和多個指標。現在,您可以更輕鬆地針對 Google Analytics (分析) API 傳回的時間序列進行進階分析。