Uzupełnianie brakujących wartości w żądaniach dotyczących daty

Nick Mihailovski, zespół Google Analytics API – październik 2009 r.

Z tego artykułu dowiesz się, jak wykrywać i uzupełniać brakujące wartości ciągów czasowych w danych zwróconych przez interfejs API eksportu danych Google Analytics.


Zanim zaczniesz

W tym artykule zakładamy, że wiesz, jak działa interfejs Google Analytics Data Export API. Przykładowy kod jest w języku Java, ale możesz używać pojęć w wybranym języku. Kod tego artykułu jest dostępny na licencji open source i można go pobrać z usługi hostingu projektów.

Z tego artykułu dowiesz się:

  • Sposób traktowania przez interfejs API eksportu danych Google Analytics wymiarów daty
  • Jak uporządkować zapytania, aby pogrupować wyniki i wykrywać brakujące daty.
  • Jak uzupełnić brakujące wartości za pomocą Javy.

Wstęp

Porównanie danych w różnych okresach daje kontekst. Na przykład stwierdzenie, że witryna wygenerowała 1 mln zł przychodów, niewiele mówi. Jednak stwierdzenie, że witryna zwiększyła przychody 10-krotnie w ujęciu kwartalnym lub rocznym, jest naprawdę imponujące. W interfejsie Google Analytics API można łatwo obserwować dane w czasie przy użyciu wymiarów ga:date, ga:day i ga:month.

Jeśli w zapytaniu używany jest tylko wymiar daty, a dowolne dni w zakresie dat nie uzyskały żadnych danych, interfejs Google Analytics API uzupełni daty i wartości 0 w danych.

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

Staje się jednak trudne, jeśli zapytanie obejmuje datę i inne wymiary. Jeśli jedna z dat nie zawiera danych, interfejs API NIE zwróci wpisu dotyczącego tej daty. Spowoduje to tylko przejście do następnej dostępnej daty zawierającej dane.

ga:keywordga:datega:sessions
krzesło2010-03-0155
krzesło2010-03-0348

W idealnej sytuacji analitycy chcieliby uzupełnić brakujące daty dla określonego słowa kluczowego, jak w pierwszym przykładzie powyżej.

W tym artykule opisujemy kilka sprawdzonych metod pragmatycznego uzupełniania danych.

Wprowadzenie

Najpierw przyjrzyjmy się temu problemowi. Są 2 powody.

  1. Google Analytics przetwarza tylko zgromadzone dane. Jeśli nikt nie odwiedził witryny w określonym dniu, nie ma danych do przetworzenia, więc żadne dane nie są zwracane.
  2. Bardzo trudno jest określić, ile dodatkowych wymiarów i jakich wartości należy używać w przypadku dat, dla których nie ma dostępnych danych.

Zamiast więc określać jeden proces i rządzić nimi wszystkie, interfejs Google Analytics API pozostawia deweloperowi możliwość wypełniania danych w przypadku zapytań z kilkoma wymiarami. Masz szczęście :)

Omówienie programu

Aby uzupełnić dane z wykresu powyżej, wykonaj te czynności.

  1. Zmodyfikuj zapytanie, aby mieć pewność, że wymiary będą oportunistyczne.
  2. Określ oczekiwane daty na podstawie zakresu dat.
  3. Powtórz i uzupełnij brakujące daty.
  4. Uzupełnij pozostałe brakujące wartości.

Modyfikowanie zapytania

Aby uzupełniać daty, musimy upewnić się, że dane zwracane przez interfejs API mają format, który pozwala łatwo wykryć, czy brakuje daty. Oto przykładowe zapytanie, które pozwala pobrać zarówno ga:keyword, jak i ga:date w ciągu pierwszych 5 dni marca:

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

Gdy zapytanie zostanie wysłane do interfejsu API, wyniki będą zawierać listę obiektów DataEntry. Każdy obiekt wpisu reprezentuje wiersz danych oraz nazwy i wartości wymiarów/danych. Nie użyto żadnego parametru sortowania, więc wyniki są zwracane w dowolnej kolejności.

ga:keywordga:datega:entrances
krzesło2010-03-0414
krzesło2010-03-0123
stół2010-03-0418
stół2010-03-0224
krzesło2010-03-0313

Aby ułatwić znajdowanie brakujących dat, musimy najpierw pogrupować wszystkie wymiary. Możesz to zrobić, ustawiając parametr sortowania zapytania na wymiary używane w oryginalnym zapytaniu.

dataQuery.setSort("ga:keyword,ga:date");

Dodanie parametru sortowania spowoduje, że interfejs API zwróci wyniki w odpowiedniej kolejności.

ga:keywordga:datega:entrances
krzesło2010-03-0123
krzesło2010-03-0313
krzesło2010-03-0414
stół2010-03-0224
stół2010-03-0418

W drugim kroku sprawdź, czy dla każdego wymiaru wszystkie daty są zwracane w kolejności rosnącej. Interfejs Google Analytics API udostępnia wiele wymiarów daty, ale tylko ga:date można sortować w granicach dat (np. dni, miesięcy, lat). Jeśli więc chcesz uzupełniać daty, upewnij się, że zapytanie używa wymiaru ga:date w wymiarach i sortowaniu parametrów zapytania.

Po wykonaniu posortowanego zapytania wszystkie te same strony docelowe zostaną zwrócone obok siebie, a daty będą ułożone w kolejności. Listę dat dla pojedynczej strony docelowej można traktować jako ciąg czasowy, a ponieważ są one uporządkowane, znacznie łatwiej jest znaleźć brakujące daty.

Określ oczekiwane daty

Aby wykryć brakujące daty, musimy porównać rzeczywiste daty zwrócone przez interfejs API z oczekiwanymi datami we wszystkich ciągach czasowych. Możemy określić, czego można się spodziewać, na podstawie:

  1. Określam oczekiwaną datę rozpoczęcia na podstawie zapytania do interfejsu API.
  2. Zliczanie oczekiwanej liczby dni w zakresie dat zapytania.

Obie wartości można używać razem do określenia każdej oczekiwanej daty przez zwiększenie daty rozpoczęcia o 1 dla każdego dnia w zakresie dat.

Określanie oczekiwanej daty rozpoczęcia

Jako oczekiwanej daty rozpoczęcia serii możemy użyć parametru zapytania start-date. Ponieważ format daty zwrócony w odpowiedzi interfejsu API yyyyMMdd jest inny niż format parametru zapytania yyyy-MM-dd, musimy najpierw przekonwertować format daty, zanim będziemy mogli go użyć.

Metoda setExpectedStartDate konwertuje formaty dat.

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

Obliczanie oczekiwanej liczby dni

Aby uzyskać liczbę dni w zakresie dat, program analizuje daty rozpoczęcia i zakończenia w obiektach Java Date. Następnie za pomocą obiektu Calendar oblicza czas między obiema datami. Do różnicy w datach dodawany jest jeden dzień, aby liczba była brana pod uwagę.

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

Teraz mamy wszystkie dane potrzebne do znalezienia brakujących dat.

Zidentyfikuj każdy ciąg czasowy w wynikach

Po wykonaniu zapytania program przechodzi przez każdy obiekt DataEntry w odpowiedzi interfejsu API. Ponieważ zapytanie zostało początkowo posortowane, odpowiedź będzie miała częściowy ciąg czasowy dla każdego słowa kluczowego. Musimy więc znaleźć początek każdego ciągu czasowego, a następnie przejrzeć każdą datę i uzupełnić brakujące dane, które nie zostały zwrócone przez interfejs API.

Ten program używa zmiennych dimensionValue i tmpDimensionValue do wykrywania początku każdej serii.

Oto cały kod obsługi odpowiedzi. Uzupełnianie brakujących danych zostało omówione poniżej.

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

Uzupełnij wszystkie brakujące daty

Dla każdego wpisu w serii program przechowuje wartości danych (wejści) w elemencie ArrayList o nazwie row. Po wykryciu nowego ciągu czasowego tworzony jest nowy wiersz, a oczekiwana data jest ustawiona na oczekiwaną datę rozpoczęcia.

Następnie przy każdym wpisie program sprawdza, czy wartość daty we wpisie jest zgodna z oczekiwaną datą. Jeśli są równe, do wiersza zostaną dodane dane podane we wpisie. W przeciwnym razie program wykrył brakujące daty, które należy uzupełnić.

Metoda backfillRow obsługuje uzupełnianie danych. Jako parametry akceptuje jako parametry oczekiwane i znalezione daty oraz bieżący wiersz. Następnie określa liczbę dni między 2 datami (niewykluczające) i dodaje tę liczbę do wiersza.

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

Gdy ta metoda się zakończy, wiersz zostanie wypełniony danymi, co umożliwi dodanie bieżących danych. Oczekiwana data jest następnie zwiększana do jednego dnia po znalezionej dacie za pomocą metody 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 "";
}

Wypełnij wszelkie pozostałe wartości

Po przetworzeniu danych z serii w elemencie row musimy sprawdzić, czy na jej końcu nie ma więcej brakujących dat.

Metoda forwardFillRow po prostu oblicza różnicę między liczbą dni w pierwotnym zapytaniu a bieżącą wielkością wiersza, a następnie dodaje tę liczbę do końca wiersza, używając liczby 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);
    }
  }
}

Na tym etapie program wypełnił wszelkie brakujące wartości w ciągu czasowym. Teraz gdy masz już wszystkie dane, program wyświetla wartości wymiarów i danych w postaci listy rozdzielonej przecinkami.

Podsumowanie

Korzystając z tego przykładu, możesz łatwo uzupełnić dane w datach, które nie zostały zwrócone przez interfejs API. Jak już wspomnieliśmy, to rozwiązanie można dostosować do dowolnego języka programowania. Deweloperzy mogą nawet dostosowywać te techniki i stosować je do obsługi wielu wymiarów i danych. Rozpoczęcie zaawansowanej analizy ciągów czasowych zwracanych przez interfejs Google Analytics API jest teraz łatwiejsze niż kiedykolwiek.