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

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

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

الخلاصة

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