מילוי ערכים חסרים בבקשות תאריך

ניק מיכאילובסקי, צוות Google Analytics API – אוקטובר 2009

במאמר הזה נסביר איך לזהות ולמלא את הערכים החסרים של סדרות זמנים בנתונים שהוחזרו מ- Data Export API של Google Analytics.


לפני שתתחיל

המאמר מתבסס על ההנחה שאתם יודעים איך פועל ה-API לייצוא נתונים של Google Analytics. הקוד לדוגמה הוא ב-Java, אבל אפשר להשתמש במושגים בשפה לבחירתך. קוד המאמר הזה מסופק כקוד פתוח, ואפשר להוריד אותו מאירוח הפרויקטים.

המאמר הזה יעזור לכם:

  • איך 'ממשק API לייצוא נתונים' של Google Analytics מתייחס למאפייני תאריך.
  • איך לבנות את השאילתות כדי לקבץ תוצאות ולזהות תאריכים חסרים.
  • כיצד למלא את הערכים החסרים באמצעות Java.

מבוא

השוואת נתונים לאורך תקופת זמן מספקת הקשר. לדוגמה, הצהרה על אתר שהניב הכנסה של מיליון דולר לא אומרת הרבה. עם זאת, הצהרה על כך שההכנסות באתר גדלו פי 10 ברבעון בהשוואה לרבעון או בהשוואה לשנה שעברה זה בהחלט מרשים. Google Analytics API מאפשר להציג בקלות נתונים לאורך זמן באמצעות המאפיינים ga:date, ga:day ו-ga:month.

אם בשאילתה שלך נעשה שימוש רק במאפיין תאריך, אם היו ימים בטווח התאריכים שנאספו אפס נתוני API, ה-API של Google Analytics ימלא את החוסרים (backfill) של התאריכים ושל הערכים 0 עבור מדדים.

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

עם זאת, קשה יותר להגדיר תאריך שאילתה יחד עם מאפיינים אחרים. אם אין נתונים באחד מהתאריכים, ה-API לא יחזיר רשומה של התאריך הזה. הקוד פשוט ידלג לתאריך הזמין הבא שמכיל נתונים.

ga:keywordga:datega:sessions
כסא2010-03-0155
כסא2010-03-0348

באופן אידיאלי, אנליסטים ירצו שהתאריכים החסרים של מילת מפתח מסוימת יוזנו כמו בדוגמה הראשונה שלמעלה

במאמר הזה מתוארות כמה שיטות מומלצות למילוי חוסרים (backfill) של נתונים באופן פרגמטי.

רקע

תחילה נראה מדוע הבעיה הזו קיימת. יש שתי סיבות לכך.

  1. מערכת Google Analytics מעבדת רק נתונים שנאספים. אם אף אחד לא נכנס לאתר ביום מסוים, לא קיימים נתונים לעיבוד ולכן לא מוחזרים נתונים.
  2. קשה מאוד לקבוע כמה מאפיינים נוספים ובאילו ערכים להשתמש עבור תאריכים שאין בהם נתונים.

לכן, במקום לנסות להגדיר תהליך אחד לשליטה בכולם, ה-API של Google Analytics כבר לא יכול למלא נתונים לשאילתות שיש להן מאפיינים מרובים. מזל טוב :)

סקירה כללית של התוכנית

אלה השלבים למילוי חוסרים (backfill) של הנתונים בתרשים שלמעלה.

  1. משנים את השאילתה כדי לוודא שהמאפיינים ממוינים באופן אוטומטי.
  2. קובעים את התאריכים הצפויים בטווח התאריכים.
  3. מבצעים איטרציה וממלאים את החוסרים (backfill) של התאריכים החסרים.
  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 אובייקטים. כל אובייקט רשומה מייצג שורה של נתונים וכולל שמות וערכים של מאפיינים/מדדים. מכיוון שלא נעשה שימוש בפרמטר מיון, התוצאות מוחזרות בסדר שרירותי.

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

ספירה של מספר הימים הצפויים

כדי לקבל את מספר הימים בטווח התאריכים, התוכנית מנתחת את תאריכי ההתחלה והסיום לאובייקטים של Date ב-Java. אחר כך משתמשים באובייקט 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 מטפלת במילוי חוסרים של נתונים. הפרמטר מקבל כפרמטרים את התאריכים הצפויים והתאריכים שנמצאו, וגם את השורה הנוכחית. לאחר מכן היא קובעת את מספר הימים בין שני התאריכים (לא כולל) ומוסיפה את מספר ה-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.