ملء القيم المفقودة من طلبات التاريخ

ميهايلوفسكي، فريق Google Analytics API – تشرين الأول (أكتوبر) 2009

تناقش هذه المقالة كيفية رصد قيم السلسلة الزمنية المفقودة وملؤها في البيانات التي يتم عرضها من واجهة برمجة التطبيقات لتصدير البيانات في "إحصاءات Google".


قبل البدء

تفترض المقالة أنّك على دراية بآلية عمل واجهة برمجة التطبيقات لتصدير البيانات في "إحصاءات Google". يتوفر الرمز النموذجي بلغة Java، ولكن يمكنك استخدام المفاهيم باللغة التي تختارها. يتم توفير رمز هذه المقالة على أنّه برنامج مفتوح المصدر ويمكن تنزيله من استضافة المشروع.

بعد قراءة هذه المقالة، ستتعرّف على ما يلي:

  • كيفية تعامُل واجهة برمجة التطبيقات لتصدير البيانات في "إحصاءات Google" مع أبعاد التاريخ.
  • كيفية تنظيم طلبات البحث في تجميع النتائج واكتشاف التواريخ المفقودة
  • كيفية ملء القيم المفقودة باستخدام Java.

مقدمة

توفر مقارنة البيانات خلال فترة زمنية السياق. على سبيل المثال، إنّ ذِكر أنّ قيمة أرباح أحد المواقع الإلكترونية قد بلغت مليون دولار أمريكي (أو ما يعادله بالعملة المحلية) لا يعني ذلك الكثير. مع ذلك، من المذهل أنّ الإعلان عن زيادة أرباح أحد المواقع الإلكترونية بمقدار 10 أضعاف مقارنةً بالربع السنوي السابق أو مقارنةً بالعام السابق هو أمر مثير للإعجاب. من خلال واجهة برمجة التطبيقات "إحصاءات Google"، أصبح من السهل تنظيم البيانات بمرور الوقت باستخدام السمات ga:date وga:day وga:month.

إذا كان طلب البحث يستخدم سمة تاريخ فقط، إذا كان هناك أي أيام في النطاق الزمني لم يتم فيه جمع أي بيانات، ستضيف Google Analytics API التواريخ و0 القيم للمقاييس.

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

ومع ذلك، يصبح من الصعب إذا طلبت البحث عن التاريخ مع سمات أخرى. وإذا لم يتضمّن أحد التاريخَين بيانات، لن تعرض واجهة برمجة التطبيقات إدخالاً لذلك التاريخ. سيتم الانتقال إلى التاريخ المتاح التالي الذي يحتوي على بيانات.

ga:keywordga:datega:sessions
كرسي2010-03-0155
كرسي2010-03-0348

من الناحية المثالية، يرغب المحللون في ملء التواريخ المفقودة لكلمة رئيسية معينة مثل المثال الأول أعلاه

توضّح هذه المقالة بعض أفضل الممارسات لإضافة البيانات السابقة بشكل عملي.

الخلفية

لنلقِ نظرة أولًا على سبب حدوث هذه المشكلة. هناك سببان.

  1. تعالج "إحصاءات Google" البيانات التي يتم جمعها فقط. وإذا لم يأت أي مستخدم إلى الموقع الإلكتروني في يوم معيّن، لن تكون هناك بيانات لمعالجة، وبالتالي لن يتم عرض أي بيانات.
  2. من الصعب جدًا تحديد عدد السمات الإضافية والقيم التي يجب استخدامها للتواريخ التي لا تتضمّن بيانات.

لذا، بدلاً من محاولة تحديد عملية واحدة لقاعدة كل هذه البيانات، تترك واجهة برمجة التطبيقات Google Analytics API عملية ملء البيانات لطلبات البحث التي تشمل أبعادًا متعددة على عاتق المطوّر. الحظّ :)

نظرة عامّة حول البرنامج

في ما يلي الخطوات اللازمة لتعبئة البيانات السابقة في الرسم البياني أعلاه.

  1. عدِّل طلب البحث لضمان ترتيب الأبعاد بطريقة محسَّنة.
  2. حدِّد التواريخ المتوقّعة من النطاق الزمني.
  3. كرر وإعادة ملء أي تواريخ مفقودة.
  4. املأ أي قيم مفقودة متبقية.

تعديل طلب البحث

لإضافة التواريخ مرة أخرى، علينا التأكّد من أنّ البيانات المعروضة من واجهة برمجة التطبيقات بتنسيق يسهّل تحديد ما إذا كان التاريخ غير متوفّر. في ما يلي مثال على طلب بحث لاسترداد كل من 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");

بعد إرسال الطلب إلى واجهة برمجة التطبيقات، ستتضمّن النتائج قائمة بكائنات 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");

ستؤدي إضافة مَعلمة sort إلى عرض النتائج بالترتيب الذي تريده في واجهة برمجة التطبيقات.

ga:keywordga:datega:entrances
كرسي2010-03-0123
كرسي2010-03-0313
كرسي2010-03-0414
جدول2010-03-0224
جدول2010-03-0418

الخطوة الثانية هي التأكّد من عرض جميع التواريخ تصاعديًا لكل سمة. مع أنّ واجهة برمجة تطبيقات "إحصاءات Google" توفّر عددًا من سمات التاريخ، لا يمكن ترتيب سوى ga:date بدقة وفقًا لحدود التاريخ (أي الأيام والأشهر والسنوات). لذلك، إذا كنت تريد إعادة تعبئة التواريخ، تأكَّد من أنّ طلب البحث يستخدم السمة ga:date في كل من السمات ومَعلمات ترتيب الطلبات.

بعد تنفيذ طلب البحث المرتَّب، سيتم عرض جميع الصفحات المقصودة نفسها بجانب بعضها بعضًا وسيتم عرض التواريخ بترتيب تسلسلي. ويمكن اعتبار قائمة تواريخ صفحة مقصودة واحدة كسلسلة زمنية، وبما أنّها مرتبة، من السهل تحديد التواريخ غير المتوفرة.

تحديد التواريخ المتوقعة

لرصد التواريخ غير المتوفّرة، علينا مقارنة التواريخ الفعلية التي تعرضها واجهة برمجة التطبيقات بالتواريخ المتوقّعة في كل سلسلة زمنية. يمكننا معرفة ما هو متوقع من:

  1. تحديد تاريخ البدء المتوقّع من طلب بيانات من واجهة برمجة التطبيقات.
  2. حساب عدد الأيام المتوقعة في النطاق الزمني لطلب البحث.

يمكن استخدام كلتا القيمتين معًا لتحديد كل تاريخ متوقّع عن طريق زيادة تاريخ البدء بمقدار 1 لكل يوم في النطاق الزمني.

تحديد تاريخ البدء المتوقع

يمكننا استخدام مَعلمة طلب البحث start-date كتاريخ البدء المتوقّع للسلسلة. بما أنّ تنسيق التاريخ المعروض في ردّ واجهة برمجة التطبيقات 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 في استجابة واجهة برمجة التطبيقات. وبما أنّه تم ترتيب طلب البحث في البداية، سيحصل الرد على سلسلة زمنية جزئية لكل كلمة رئيسية. لهذا السبب، علينا تحديد بداية كل سلسلة زمنية، ثم مراجعة كل تاريخ وملء البيانات غير المتوفرة التي لا تعرضها واجهة برمجة التطبيقات.

يستخدم هذا البرنامج المتغيّرَين 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);
    }
  }
}

في هذه المرحلة، ملأ البرنامج أي قيم مفقودة في السلسلة الزمنية. بعد أن أصبحت لدينا جميع البيانات، يطبع البرنامج قيم السمات والمقاييس في شكل قائمة مفصولة بفواصل.

الخاتمة

باستخدام هذا النموذج، يمكنك بسهولة إضافة بيانات سابقة للتواريخ التي لا تعرضها واجهة برمجة التطبيقات. كما ذكرنا أعلاه، يمكن تعديل هذا الحل ليلائم أي لغة برمجة. ويمكن للمطوّرين أيضًا تعديل هذه الأساليب وتطبيقها للتعامل مع أبعاد ومقاييس متعددة. أصبح الآن بدء إجراء تحليل متقدِّم لسلاسل المدد التي تعرضها واجهة برمجة التطبيقات "إحصاءات Google" أسهل من أي وقت مضى.