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

"نيك ميلوفسكي"، فريق "إحصاءات Google" لواجهة برمجة التطبيقات – تشرين الأول (أكتوبر) 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" في عمليّة ملء البيانات في طلبات البحث التي تتضمّن أبعادًا متعدّدة وصولاً إلى مطوّر البرامج. يَا لَلرَّوْعَة :)

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

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

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

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

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 الفرق بين عدد الأيام في طلب البحث الأصلي وحجم الصف الحالي، وتضيف هذه القيمة العديد إلى 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);
    }
  }
}

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

الخاتمة

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