2017-07-27 5 views
1

Ich versuche, Wetterdaten für 7 Tage vor einem bestimmten Datum und in der Nähe von einigen Koordinaten (lat, lon) zu bekommen. Etwas wie 20km Radius. Wenn es mehrere Stationen gibt, würde ich wahrscheinlich die Daten nach Tagen gruppieren wollen.Historische Wetterdaten BigQuery

Gibt es eine Möglichkeit, all dies direkt mit BigQuery zu berechnen? Für den Test habe ich berechnet min und max koordiniert und erstellt die folgende Abfrage

SELECT 
    * 
FROM 
    [bigquery-public-data:noaa_gsod.gsod2016] a 
JOIN 
    [bigquery-public-data:noaa_gsod.stations] b 
ON 
    a.stn=b.usaf 
    AND a.wban=b.wban 
WHERE 
    (b.lat >= 46.248332 
    AND b.lat <= 47.147654) 
    AND (b.lon >= 5.689853 
    AND b.lon <= 7.001115) 
    AND a.mo='03' 

ich mit der Abfrage nicht sehr glücklich bin noch

  • Es ist nicht Durchschnitt über mehrere Stationen pro Tag Er wählt alle Daten für einen bestimmten Monat.
  • Wie kann ich 7 Tage eines bestimmten Datums erreichen?
  • Können max und min lat/lon direkt über die Abfrage berechnet werden?
  • Sehr häufig findet es keine Daten, da der Radius 20km von höchstwahrscheinlich zu klein ist, um eine Station zu finden. Wie kann ich die Abfrage auf ändern, um die nächstgelegenen Stationen zu finden, wenn sie nicht innerhalb von 20 km gefunden werden können?
  • Gibt es bessere, kostenlose, historische Wetterdaten, die ich bekommen kann?

Dies ist, wie ich die min max Koordinaten berechnen:

maxLat = lat + math.degrees(searchRadius/earthRadius) 
minLat = lat - math.degrees(searchRadius/earthRadius) 
maxLon = lon + math.degrees(searchRadius/earthRadius)/math.cos(math.radians(lat)) 
minLon = lon - math.degrees(searchRadius/earthRadius)/math.cos(math.radians(lat)) 
+0

Bitte zeigen Sie einige Daten, die Ihre Frage klären; Ich folge es nicht vollständig. –

+0

welcher Teil ist nicht klar? Wenn Sie die obige Abfrage ausführen, erhalten Sie Daten von mehreren Stationen. Sie sollten nach Tagen und Monaten gruppiert werden. Außerdem zeigt die Abfrage nur Daten für einen bestimmten Monat an. Aber ich möchte lieber die letzte Woche eines bestimmten Datums haben. – Chris

+0

Keine direkte Antwort, aber das [Beispiel im Migrationsleitfaden] (https://cloud.google.com/bigquery/docs/reference/standard-sql/migrating-from-legacy-sql#correlated_subqueries) könnte nützlich sein. –

Antwort

2

Dies ist die beste Lösung, die ich tun konnte:

#standardSQL 
CREATE TEMP FUNCTION distance(lat1 FLOAT64, lat2 FLOAT64, lon1 FLOAT64, lon2 FLOAT64) AS((
WITH data AS(
SELECT POW(SIN((ACOS(-1)/180 * (lat1 -lat2))/2), 2) + COS(ACOS(-1)/180 * (lat1)) * COS(ACOS(-1)/180 * (lat2)) * POW(SIN((ACOS(-1)/180 * (lon1 -lon2))/2), 2) a 
) 
SELECT 6371 * 2 * ATAN2(SQRT((SELECT a FROM data)), SQRT(1 - (SELECT a FROM data))) 
)); 

WITH temperature_data AS(
SELECT 
    CONCAT(year, mo, da) date, 
    temp, 
    b.lat lat, 
    b.lon lon 
FROM `bigquery-public-data.noaa_gsod.gsod2016` a 
JOIN `bigquery-public-data.noaa_gsod.stations` b 
ON a.stn = b.usaf AND a.wban = b.wban 
WHERE concat(year, mo, da) BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(PARSE_DATE('%Y%m%d', '20160725'), INTERVAL 7 DAY)) AND '20160725' 
) 

SELECT 
    date, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS std_temp) data_20km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS std_temp) data_50km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS std_temp) data_100km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS std_temp) data_200km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS std_temp) data_500km 
FROM temperature_data t 
WHERE 
distance(t.lat, 10.1, t.lon, 10.2) < 2000 
GROUP BY date 
ORDER BY date 

Ich werde versuchen zu erklären, zusammen mit Ihren Fragen:

Wie kann ich 7 Tage eines bestimmten Datums erhalten?

Innerhalb der Abfrage temperature_data, bemerken es WHERE Klausel hat die Bedingung:

WHERE concat(year, mo, da) BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(PARSE_DATE('%Y%m%d', '20160725'), INTERVAL 7 DAY)) AND '20160725' 

Hier werden die letzten 7 Tage von einem bestimmten Zeitpunkt ausgewählt werden. Sie können das Datum auswählen, das Sie analysieren möchten, indem Sie einfach den Wert '20160725' ändern.

Können max und min lat/lon direkt über die Abfrage berechnet werden?

Ja. Ich stelle mir vor, du meinst damit, wenn es möglich ist, räumliche Punkte innerhalb eines bestimmten Bereichs auszuwählen (zum Beispiel 20 km). Eine Möglichkeit, dies zu tun ist, die eine temporäre Funktion Abstände zwischen einem gewünschten Punkt und den Stationspunkten zu berechnen, die in der Abfrage ausgedrückt wird durch:

CREATE TEMP FUNCTION distance(lat1 FLOAT64, lat2 FLOAT64, lon1 FLOAT64, lon2 FLOAT64) AS((
WITH data AS(
SELECT POW(SIN((ACOS(-1)/180 * (lat1 -lat2))/2), 2) + COS(ACOS(-1)/180 * (lat1)) * COS(ACOS(-1)/180 * (lat2)) * POW(SIN((ACOS(-1)/180 * (lon1 -lon2))/2), 2) a 
) 
SELECT 6371 * 2 * ATAN2(SQRT((SELECT a FROM data)), SQRT(1 - (SELECT a FROM data))) 
)); 

Sie sich um und testen Sie diese Funktion zum Beispiel spielen können:

SELECT distance(50, 60, 30, 10) # result is ~ 1680km 

Diese Funktion wird hier verwendet:

WHERE 
distance(t.lat, 10.1, t.lon, 10.2) < 2000 

zum ausfiltern von mehr als 2000 km Punkte weiter weg von (10.1 °, 10.2 °). In Ihrer Abfrage können Sie anstelle von (10,1 °, 10,2 °) einen anderen Eingabewert wählen.

Sehr oft findet es keine Daten, da der Radius von 20km wahrscheinlich zu klein ist, um eine Station zu finden. Wie kann ich die Abfrage auf ändern, um die nächstgelegenen Stationen zu finden, wenn sie nicht innerhalb von 20 km gefunden werden können?

Eine mögliche Lösung wird auf einmal für mehrere unterschiedliche Abstände abfragt:

SELECT 
    date, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS std_temp) data_20km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS std_temp) data_50km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS std_temp) data_100km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS std_temp) data_200km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS std_temp) data_500km 
FROM temperature_data t 
WHERE 
distance(t.lat, 10.1, t.lon, 10.2) < 2000 
GROUP BY date 

Hinweis, dass diese Abfragestation Punkte Extrahieren zwischen dem Eingangspunkt (10,1 °, 10,2 °) bis zu 2000 km entfernt. Und dann wird ein Filter verwendet, um Punkte innerhalb der Bereiche 20km, 50km, 100km, 200km und 500km auszuwählen.

Sie können diese Werte ändern, wie Sie es für richtig halten. Wenn Sie die Durchschnittstemperatur von einem anderen Punkt (40 °, 30 °) erhalten möchten, ändern Sie einfach die Werte (10.1, 10.2) auf (40, 30) und Sie können loslegen. Wenn Sie unterschiedliche Abstände von diesem Punkt wünschen, können Sie die Ausdrücke IF(distance(t.lat, 10.1, t.lon, 10.2) < 200 beispielsweise in einen Bereich ändern, der Ihren Anforderungen besser entspricht.

Beachten Sie, dass die WHERE Klausel hat die Bedingung:

distance(t.lat, 10.1, t.lon, 10.2) < 2000 

So wird diese alle Stationen Ausfiltern weiter weg von dem Punkt (10.1, 10.2) von mehr als 2000 km. Sie können diesen Wert auch ändern, wie Sie es für richtig halten.

Abschließender Hinweis zu diesem: Ich brachte auch die STDDEV_SAMP, die die standard deviation of a sampling ist. Dies kann sowohl für Sie als auch für eine Vorstellung davon nützlich sein, wie sehr sich der Durchschnitt um den Mittelwert herum ausbreitet (korrigiert durch den Effekt der Sampling-Datengröße). Der Durchschnitt an sich ist nicht so wertvoll, wenn wir nicht wissen, wie nahe wir dem richtigen Wert sind.

Gibt es bessere, kostenlose, historische Wetterdaten, die ich bekommen kann?

Weiß nicht. Hoffentlich wird dieser öffentliche Datensatz für Sie gut genug sein.

+0

Große Lösung und funktioniert wie ein Charme! – Chris

+0

@Chris Glücklich zu wissen, dass es funktioniert :)! –

0

Mit der Informationen, die Sie Ich bin nicht sicher gegeben haben, wenn Sie die max/min Daten in Abfrage berechnen kann. Wenn ich in Legacy SQL arbeite, würde ich wahrscheinlich versuchen, mehrere Abfragen zu verschachteln oder sich einer Abfrage anzuschließen, die sie berechnet, oder beides.

Sie könnten auch in der Lage sein, etwas zu schreiben, das die Suchanfrage bei Bedarf anpasst, aber ich bekomme einfach nicht die Struktur von dem, was Sie tun, bereits genug, um einen Vorschlag zu schreiben.

Für die anderen Fragen:

Erste mittelt - anstatt * mit alles rufen Sie festlegen, welche Spalten nennen müssen einzeln werden mitteln und die durch ignorieren oder Gruppe.

Die letzten 7 Tage eines bestimmten Datums auswählen - es ist sehr bedauerlich, dass es keine Zeitstempelspalte zu geben scheint, also müssen Sie eine erzwingen.

In LegacySQL würde ich so etwas schreiben:

SELECT dte, avg_temp, avg_cnt_temp 
FROM 
(SELECT CAST(CONCAT(a.year, '-', a.mo, '-', a.da) AS timestamp) AS dte, 
/* This is calling the separate year, month, and day strings as a 
datetime funtion so I can use date_add later */ 
AVG(a.temp) AS avg_temp, AVG(a.count_temp) AS avg_cnt_temp /* You'll 
want to include all of the data you're wanting to call here, I 
only tested with these two */ 
FROM [bigquery-public-data:noaa_gsod.gsod2016] AS a 
JOIN [bigquery-public-data:noaa_gsod.stations] AS b 
ON a.stn=b.usaf AND a.wban=b.wban 
GROUP BY dte, mo, da) 
WHERE dte >= (DATE_ADD('2016-12-31 00:00:00', -7, "DAY")) AND dte <= 
TIMESTAMP('2016-12-31 00:00:00') /* replace with your date */ 

ich in Standard-SQL denken Sie die gleiche Art und Weise nicht verschachtelt tun.

Sie eine Stationskennung nicht anrufen, wenn Sie Daten über Stationen kombinieren möchten, usw.