Nick Mihailovski,Google Analytics API 小组 - 2009 年 10 月
本文讨论了如何检测和回填 Google Analytics(分析)Data Export API 返回的数据中缺少的时间序列值。
准备工作
本文假定您了解 Google Analytics(分析)Data Export API 的工作原理。示例代码使用 Java 编写,但您可以采用自己选择的语言使用这些概念。本文中的代码作为开放源代码提供,可以通过 Project Hosting 下载。
阅读完本文后,您将了解以下内容:
- Google Analytics(分析)Data Export API 如何处理日期维度。
- 如何构建查询以便对结果分组并检测缺少的日期。
- 如何使用 Java 填充缺少的值。
简介
跨时段比较数据可提供背景信息。例如,声明一个网站收入 100 万美元没有多大意义。但声明一个网站的季度或年度收入增长 10 倍则意义重大。借助 Google Analytics(分析)API,您可以使用 ga:date
、ga:day
和 ga:month
维度轻松绘制数据随时间变化的图表。
如果您的查询仅使用日期维度,并且该日期范围内的某些天未收集任何数据,那么 Google Analytics(分析)API 将回填日期和 0
值以便获得相关指标。
ga:date | ga:sessions |
---|---|
2010-03-01 | 101 |
2010-03-02 | 0 |
2010-03-03 | 69 |
但是,如果您查询日期和其他维度,则情况会变得非常复杂。如果某个日期没有任何数据,则 API 不会为该日期返回条目,而只会跳转到包含数据的下一可用日期。
ga:keyword | ga:date | ga:sessions |
---|---|---|
chair | 2010-03-01 | 55 |
chair | 2010-03-03 | 48 |
理想情况下,分析人员希望像上述第一个示例那样填充特定关键字缺少的日期
本文介绍了以编程方式回填数据的一些最佳做法。
背景
我们首先看一下为什么会产生此问题。原因有两个。
- Google Analytics(分析)仅处理收集的数据。如果在某个特定日期无人访问网站,则没有要处理的数据,因此不会返回任何数据。
- 确定有多少其他维度以及对没有数据的日期使用什么值非常困难。
因此,Google Analytics(分析)API 并不尝试定义一个过程来规范一切,而是将为具有多个维度的查询填充数据的任务留给开发者完成。您真幸运 :)
程序概览
下面是回填上图数据的一些步骤。
- 修改查询以确保随机对维度排序。
- 根据日期范围确定预计日期。
- 迭代和回填任何缺少的日期。
- 填充任何其他缺少的值。
修改查询
如需回填日期,我们需要确保从 API 返回的数据的格式能够在缺少日期时轻松检测。下面是一个查询示例,它检索 3 月前 5 天的 ga:keyword
和 ga: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:keyword | ga:date | ga:entrances |
---|---|---|
chair | 2010-03-04 | 14 |
chair | 2010-03-01 | 23 |
table | 2010-03-04 | 18 |
table | 2010-03-02 | 24 |
chair | 2010-03-03 | 13 |
为了便于确定缺少了哪些日期,我们首先需要组合所有维度。这可以通过将查询的排序参数设置为原始查询中使用的维度来完成。
dataQuery.setSort("ga:keyword,ga:date");
添加排序参数可让 API 按期望的顺序返回结果。
ga:keyword | ga:date | ga:entrances |
---|---|---|
chair | 2010-03-01 | 23 |
chair | 2010-03-03 | 13 |
chair | 2010-03-04 | 14 |
table | 2010-03-02 | 24 |
table | 2010-03-04 | 18 |
第二步是确保按升序返回每个维度的所有日期。虽然 Google Analytics(分析)API 提供多种日期维度,但只有 ga:date
可以跨日期边界(即日、月、年)准确排序。因此,如果您想要回填日期,请确保您的查询在维度和排序查询参数中都使用了 ga:date
维度。
在执行排序的查询之后,所有相同的着陆页都将相互紧挨着返回,并且日期按先后顺序显示。可以将单一着陆页的日期列表视为一个时间序列,因为它们是按顺序排列的,因此可以很容易找到缺少的日期。
确定预计日期
要检测缺少的日期,我们需要比较 API 返回的实际日期和每个时间序列中的预计日期。我们可以通过以下方式找出预计日期:
- 通过 API 查询确定预计开始日期。
- 计算查询日期范围中的预计天数。
可以结合使用这两个值确定每个预计日期,即用开始日期依次加 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 没有返回的缺失数据。
此程序使用 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); } }
回填任何缺少的日期
对于序列中的每个条目,该程序会将指标值(进入次数)存储在名为 row
的 ArrayList
中。在检测到新的时间序列时,该程序将创建一个新行,并将预计日期设置为预计开始日期。
然后,该程序会检查每个条目中的日期值是否等于预计日期。如果相等,则系统会将条目中的指标添加到该行中。如果不相等,该程序就是检测到了需要回填的缺少日期。
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 返回的时间序列执行高级分析。