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

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

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


לפני שתתחיל

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

לאחר קריאת המאמר הזה, נלמד אתכם:

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

מבוא

השוואת נתונים לאורך תקופת זמן מספקת הקשר. לדוגמה, פרסום של אתר שהניב הכנסה של מיליון דולר לא אומר הרבה. אבל ללא ספק, הצהרה על כך שהאתר הגדילו את ההכנסה פי 10 בהשוואה לרבעון או לשנה בהשוואה לשנה הקודמת. בעזרת ה-API של Google Analytics קל להציג נתונים לאורך זמן באמצעות המאפיינים ga:date, ga:day ו-ga: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

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

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

רקע

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

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

עכשיו יש לנו את כל הנתונים שאנחנו צריכים כדי לקבוע אילו תאריכים חסרים.

זיהוי כל סדרות בתוצאות

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