तारीख के अनुरोधों से गायब मान भरना

निक मिहैलोवस्की, Google Analytics API टीम – अक्टूबर 2009

इस लेख में, Google Analytics Data Export API से मिले डेटा में मौजूद टाइम सीरीज़ की वैल्यू का पता लगाने और उन्हें बैकफ़िल करने का तरीका बताया गया है.


शुरू करने से पहले

इस लेख में यह जानकारी दी गई है कि Google Analytics का डेटा एक्सपोर्ट एपीआई कैसे काम करता है. सैंपल कोड Java में है, लेकिन आपके पास अपनी पसंदीदा भाषा में कॉन्सेप्ट इस्तेमाल करने का विकल्प है. इस लेख के लिए कोड ओपन सोर्स के तौर पर दिया गया है और इसे प्रोजेक्ट होस्टिंग से डाउनलोड किया जा सकता है.

इस लेख को पढ़ने के बाद, आपको पता चलेगा कि:

  • Google Analytics Data Export API, तारीख वाले डाइमेंशन का इस्तेमाल कैसे करता है.
  • नतीजों को ग्रुप करने के लिए अपनी क्वेरी को कैसे व्यवस्थित करें और छूटी हुई तारीखों का पता कैसे लगाएं.
  • Java का इस्तेमाल करके छूटी हुई वैल्यू कैसे भरें.

शुरुआती जानकारी

किसी समयावधि के डेटा की तुलना करने से पता चलता है कि डेटा किस बारे में है. उदाहरण के लिए, किसी वेबसाइट ने 10 लाख डॉलर की आय जनरेट करने का कोई मतलब नहीं बताया. हालांकि, यह कहना कि वेबसाइट की आय में तिमाही या साल-दर-साल 10 गुना बढ़ोतरी हुई है, यह सच में शानदार है. Google Analytics API की मदद से, 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 Analytics, सिर्फ़ इकट्ठा किए गए डेटा को प्रोसेस करता है. अगर किसी खास दिन कोई भी साइट पर नहीं आता, तो प्रोसेस करने के लिए कोई डेटा नहीं होता. इसलिए, कोई डेटा नहीं दिखाया जाता.
  2. यह तय करना बहुत मुश्किल है कि जिन तारीखों में डेटा नहीं है उनके लिए कितने अतिरिक्त डाइमेंशन और किन वैल्यू का इस्तेमाल किया जाना चाहिए.

इसलिए, Google Analytics API उन सभी क्वेरी के लिए एक प्रोसेस तय करने के बजाय, उन क्वेरी के लिए डेटा भरने की कगार पर रखता है जिनमें डेवलपर तक के कई डाइमेंशन होते हैं. आप भाग्यशाली हैं :)

प्रोग्राम की खास जानकारी

ऊपर दिए गए चार्ट में, डेटा बैकफ़िल करने का तरीका बताया गया है.

  1. क्वेरी में बदलाव करें, ताकि यह पक्का किया जा सके कि डाइमेंशन को ज़रूरत के हिसाब से क्रम में लगाया गया है.
  2. तारीख की सीमा से अनुमानित तारीखें तय करें.
  3. जो तारीखें मौजूद नहीं हैं उन्हें दोहराएं और बैकफ़िल करें.
  4. बची हुई वैल्यू भरें.

क्वेरी में बदलाव करें

तारीखों को बैकफ़िल करने के लिए, हमें यह पक्का करना होगा कि एपीआई से मिला डेटा ऐसे फ़ॉर्मैट में हो जिससे यह आसानी से पता चल सके कि तारीख कब मौजूद नहीं थी. मार्च में पहले पांच दिनों के लिए, ga:keyword और ga:date, दोनों को वापस पाने के लिए, यहां उदाहरण के तौर पर एक क्वेरी दी गई है:

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 Analytics API, तारीख वाले कई डाइमेंशन उपलब्ध कराता है. हालांकि, सिर्फ़ ga:date को ही तारीख की सीमाओं (जैसे कि दिन, महीने, साल) में सही तरीके से क्रम में लगाया जा सकता है. इसलिए, अगर आपको तारीखें बैकफ़िल करनी हैं, तो पक्का करें कि आपकी क्वेरी में, दोनों डाइमेंशन और क्वेरी पैरामीटर को क्रम से लगाने के लिए, ga:date डाइमेंशन का इस्तेमाल किया गया हो.

क्रम से लगाई गई क्वेरी के लागू होने के बाद, सभी लैंडिंग पेज एक-दूसरे के बगल में दिखाए जाएंगे और तारीखें क्रम से दिखाई जाएंगी. किसी एक लैंडिंग पेज की तारीखों की सूची को एक टाइम सीरीज़ माना जा सकता है. साथ ही, तारीखें क्रम से होने पर, उन तारीखों की पहचान करना ज़्यादा आसान होता है जो मौजूद नहीं हैं.

अनुमानित तारीखें तय करें

छूटे हुए तारीखों का पता लगाने के लिए, हमें एपीआई से लौटाए गए असल तारीखों की तुलना, हर टाइम सीरीज़ की अनुमानित तारीखों से करनी होगी. हम पता लगा सकते हैं कि क्या उम्मीद की जा सकती है:

  1. एपीआई क्वेरी से शुरू होने की अनुमानित तारीख तय करना.
  2. क्वेरी की तारीख की सीमा में उम्मीद के मुताबिक दिनों की गिनती करना.

दोनों वैल्यू का एक साथ इस्तेमाल करके, हर अनुमानित तारीख को तय किया जा सकता है. इसके लिए, तारीख की सीमा में हर दिन के लिए, शुरू होने की तारीख में एक की बढ़ोतरी करें.

अपेक्षित आरंभ तारीख निर्धारित करना

हम सीरीज़ के शुरू होने की अनुमानित तारीख के तौर पर, 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);
    }
  }

अनुमानित दिनों की संख्या की गिनती करना

तारीख की सीमा में दिनों की संख्या पाने के लिए, प्रोग्राम शुरू और खत्म होने की तारीखों को 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 ऑब्जेक्ट से होकर गुज़रता है. शुरुआत में क्वेरी को क्रम से लगाया गया था. इसलिए, जवाब में हर कीवर्ड के लिए आंशिक टाइम सीरीज़ होगी. इसलिए, हमें हर टाइम सीरीज़ की शुरुआत का पता लगाना होगा. इसके बाद, हर तारीख को देखना होगा और वह डेटा भरना होगा जो एपीआई के ज़रिए नहीं मिला है.

यह प्रोग्राम, हर सीरीज़ की शुरुआत का पता लगाने के लिए, 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);
  }
}

कोई भी छूटा हुआ तारीख बैकफ़िल करें

सीरीज़ की हर एंट्री के लिए, प्रोग्राम मेट्रिक वैल्यू (एंट्रेंस) को row नाम के ArrayList में सेव करता है. किसी नई टाइम सीरीज़ का पता चलने पर, एक नई लाइन बनाई जाती है. साथ ही, शुरू होने की अनुमानित तारीख पर सेट कर दिया जाता है.

इसके बाद, हर एंट्री के लिए प्रोग्राम यह जांच करता है कि एंट्री में दी गई तारीख की वैल्यू आपकी उम्मीद के मुताबिक है या नहीं. अगर वे बराबर हैं, तो एंट्री की मेट्रिक, लाइन में जोड़ दी जाती है. अगर ऐसा नहीं है, तो प्रोग्राम में कुछ तारीखें मौजूद नहीं हैं जिन्हें बैकफ़िल करना ज़रूरी है.

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 Analytics API से मिलने वाली टाइम सीरीज़ का बेहतर विश्लेषण करना, पहले से कहीं ज़्यादा आसान हो गया है.