Meteorologie x Python ~ Von der Wetterdatenerfassung bis zur Spektrumanalyse ~

In den vorherigen Artikeln habe ich einige Arten des Umgangs mit Daten der Meteorologischen Agentur behandelt, aber es gibt eine andere Methode zur Datenerfassung (aktueller Status). Diese Methode ist die effizienteste), und ich werde die Analyse der erfassten Daten erleichtern.


Frühere Artikel

・ Meteorologie x Python ~ Automatische Erfassung von AMeDAS-Standortdaten ~ https://qiita.com/OSAKO/items/264c77b70843045bc12b ・ Meteorologie x Python ~ Automatische Erfassung von AMeDAS-Punktdaten (zusätzliche Ausgabe) ~ https://qiita.com/OSAKO/items/505ecee67df424963e53 ・ Meteorologie x Ruby ~ Ruby Scraping mit Mechanize ~ https://qiita.com/OSAKO/items/3c1cac0b5448be9ab243

1. Krabbeln

▶ Ich möchte die numerischen Werte oder Zeichenfolgen sofort in das folgende Tabellenformat einbetten. (Beispiel: Daten vom 1. Januar 2019 in Tokio) image.png https://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=44&block_no=47662&year=2019&month=01&day=1&view=

▶ Implementieren Sie zunächst einen Crawler, um alle URLs mit mehr als 1000 Punkten abzurufen und aufzulisten. https://www.data.jma.go.jp/obd/stats/etrn/index.php?prec_no=44&block_no=47662&year=&month=&day=&view= ↑ Beispiel für die URL, die Sie erhalten möchten ( ** Die IDs prec_no und block_no unterscheiden sich je nach Speicherort ** </ font>).

Verfahren

① Zunächst von [dieser URL](https://www.data.jma.go.jp/obd/stats/etrn/select/prefecture00.php?prec_no½block_no Jeder & Jahr = & Monat = & Tag = & Ansicht =) Ich möchte, dass auf eine URL zugegriffen wird, also suche ich nach dem href-Attribut unter dem Bereichstag und liste es auf. (Get_area_link-Methode im folgenden Code) image.png (2) Wenn Sie die URL jedes oben aufgeführten Bereichs als Schlüssel verwenden und das href-Attribut jedes Punkts auf dieselbe Weise durchsuchen, können Sie die Ziel-URL mit der ID in prec_no und block_no abrufen. (Get_station_link-Methode im folgenden Code) image.png ③ Formatieren Sie danach die aufgelistete URL ein wenig und speichern Sie sie in CSV. (Data_arange-Methode im folgenden Code) image.png

Programm

get_amedas_station_url.py


# -*- coding: utf-8 -*-
import pandas as pd
import urllib.request
from bs4 import BeautifulSoup

class Get_amedas_station:
  def __init__(self):
    url = 'https://www.data.jma.go.jp/obd/stats/etrn/select/prefecture00.php?prec_no=&block_no=&year=&month=&day=&view='
    html = urllib.request.urlopen(url)
    self.soup = BeautifulSoup(html, 'html.parser')
  
  def get_area_link(self):
    elements = self.soup.find_all('area')
    self.area_list = [element['alt'] for element in elements]
    self.area_link_list = [element['href'] for element in elements]

  def get_station_link(self):
    out = pd.DataFrame(columns=['station','url','area'])
    for area, area_link in zip(self.area_list, self.area_link_list):
      url = 'https://www.data.jma.go.jp/obd/stats/etrn/select/'+ area_link
      html = urllib.request.urlopen(url)
      soup = BeautifulSoup(html, 'html.parser')
      elements = soup.find_all('area')
      station_list = [element['alt'] for element in elements]
      station_link_list = [element['href'].strip('../') for element in elements]
      df1 = pd.DataFrame(station_list,columns=['station'])
      df2 = pd.DataFrame(station_link_list,columns=['url'])
      df = pd.concat([df1, df2],axis=1).assign(area=area)
      out = pd.concat([out,df])
      print(area)
    self.out = out
  
  def data_arange(self):
    out = self.out[~self.out.duplicated()].assign(append='https://www.data.jma.go.jp/obd/stats/etrn/')
    out['amedas_url'] = out['append'] + out['url']
    out = out.loc[:,['area','station','amedas_url']]
    out.to_csv('amedas_url_list.csv',index=None, encoding='SJIS')

if __name__ == '__main__':
  amedas = Get_amedas_station()
  amedas.get_area_link()
  amedas.get_station_link()
  amedas.data_arange()

2. Schaben

▶ In Bezug auf die Erfassung von AMeDAS-Daten, die das Hauptthema sind, habe ich dieses Mal ein Programm geschrieben, das verschiedene Zeitskalen von Daten für 1 Tag, 1 Stunde und 10 Minuten unterstützt (die Codemenge ist jedoch relativ groß und kann verschwenderisch sein, sodass dies erforderlich ist. Verbesserung) ▶ Als Einschränkung unterscheidet sich das Beobachtungselement je nachdem, welcher AMeDAS-Punkt ausgewählt ist. ( ** Der ausgewählte Punkt ist unterschiedlich, je nachdem, ob es sich um eine Wetterstation handelt oder nicht ** </ font>)


Vergleichen wir zum Beispiel die beobachteten Ereignisse zwischen Tokio und Hachioji auf jeder Zeitskala.

** ・ 1-Tages-Daten ** Tokio image.png Hachioji image.png ** ・ 1 Stunde Daten ** Tokio image.png Hachioji image.png ** ・ 10 Minuten Daten ** Tokio image.png Hachioji image.png

Programm

get_amedas_data.py


# -*- coding: utf-8 -*-
import re
import time
import numpy as np
import pandas as pd
import urllib.request
from bs4 import BeautifulSoup
from datetime import timedelta
from datetime import datetime as dt
from dateutil.relativedelta import relativedelta

#Eine Methode zum Suchen und Auflisten der Daten, die aus der URL jedes AMeDAS-Punkts erfasst werden sollen
def search_data(url):
  html = urllib.request.urlopen(url)
  time.sleep(1)
  soup = BeautifulSoup(html, 'html.parser')
  element = soup.find_all('tr', attrs={'class':'mtx', 'style':'text-align:right;'})
  out = [list(map(lambda x: x.text, ele)) for ele in element]
  return out

class Get_amedas_data:
  def __init__(self,area_name,station_name):
    #Beziehen Sie sich auf die Ziel-URL aus dem angegebenen Bereich und Punkt
    self.st_name = station_name
    self.a_name = area_name
    amedas_url_list = pd.read_csv('amedas_url_list.csv',encoding='SJIS')
    df = amedas_url_list[(amedas_url_list['area']==self.a_name) & (amedas_url_list['station']==self.st_name)]
    amedas_url = df.iat[0,2]
    #Holen Sie sich Zahlen mit regulären Ausdrücken von URL (Prec_nein und blockieren_kein Ausweis erforderlich)
    pattern=r'([+-]?[0-9]+\.?[0-9]*)'
    id_list=re.findall(pattern, amedas_url)
    self.pre_id = id_list[0]
    self.s_id = id_list[1]

  #Da 1-Tages-Daten gemeinsam als Daten für Januar erfasst werden können, geben Sie den Startmonat und den Endmonat an.
  def set_date1(self, startmonth, endmonth):
    self.start = startmonth
    self.end = endmonth
    strdt = dt.strptime(self.start, '%Y%m')
    enddt = dt.strptime(self.end, '%Y%m')
    months_num = (enddt.year - strdt.year)*12 + enddt.month - strdt.month + 1
    #Listen Sie die Monate vom Startmonat bis zum Endmonat auf
    self.datelist = map(lambda x, y=strdt: y + relativedelta(months=x), range(months_num))
  
  #Geben Sie für Daten von 1 Stunde oder 10 Minuten das Start- und Enddatum an
  def set_date2(self,startdate,enddate):
    self.start = startdate
    self.end = enddate
    strdt = dt.strptime(self.start, '%Y%m%d')
    enddt = dt.strptime(self.end, '%Y%m%d')
    days_num = (enddt - strdt).days + 1
    #Liste Daten vom Startdatum bis zum Enddatum
    self.datelist = map(lambda x, y=strdt: y + timedelta(days=x), range(days_num))

  #Erstellen Sie im Voraus einen leeren Datenrahmen. An dem Punkt, an dem sich das meteorologische Observatorium befindet, können weitere Elemente erworben werden.
  #Geben Sie die Zeitskala an, die Sie erhalten möchten (Typ)
  def dl_data(self, type):
    #Leerer Datenrahmen für tägliche Daten
    data1  = pd.DataFrame(columns=['Jahr Monat','Tag','Durchschnittlicher lokaler Druck','Durchschnittlicher Meeresspiegeldruck','Tag降水量','Bis zu 1 Stunde Niederschlag','Bis zu 10 Minuten Niederschlag','Durchschnittstemperatur','Höchste Temperatur','Niedrigste Temperatur','Durchschnittliche Luftfeuchtigkeit','Minimale Luftfeuchtigkeit','Durchschnittliche Windgeschwindigkeit','Maximale Windgeschwindigkeit','Maximale Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Tag照時間','Schneefall','Tiefster Schnee','Wetterübersicht (Mittag)','Wetterübersicht (Nacht)'])
    data1_ = pd.DataFrame(columns=['Jahr Monat','Tag','Tag降水量','Bis zu 1 Stunde Niederschlag','Bis zu 10 Minuten Niederschlag','Durchschnittstemperatur','Höchste Temperatur','Niedrigste Temperatur','Durchschnittliche Windgeschwindigkeit','Maximale Windgeschwindigkeit','Maximale Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Die meiste Windrichtung','Tag照時間','Schneefall','Tiefster Schnee'])
    #Leerer Datenrahmen für stündliche Daten
    data2  = pd.DataFrame(columns=['Datum','Zeit','Lokaler Druck','Meeresspiegeldruck','Niederschlag','Temperatur','Taupunkttemperatur','Dampfdruck','Feuchtigkeit','Windgeschwindigkeit','Windrichtung','日照Zeit間','Gesamtsonnenstrahlung','Schneefall','Schneedecke','Wetter','Wolkenvolumen','Sichtweite'])
    data2_ = pd.DataFrame(columns=['Datum','Zeit','Niederschlag','Temperatur','Windgeschwindigkeit','Windrichtung','日照Zeit間','Schneefall','Schneedecke'])
    #Leerer Datenrahmen für 10-Minuten-Daten
    data3  = pd.DataFrame(columns=['Datum','Stunde und Minute','Lokaler Druck','Meeresspiegeldruck','Niederschlag','Temperatur','Relative Luftfeuchtigkeit','Durchschnittliche Windgeschwindigkeit','Durchschnittliche Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Sonnenlichtzeit'])
    data3_ = pd.DataFrame(columns=['Datum','Stunde und Minute','Niederschlag','Temperatur','Durchschnittliche Windgeschwindigkeit','Durchschnittliche Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Sonnenlichtzeit'])

    #Erstellen Sie einen Datenrahmen, indem Sie ihn vertikal verbinden, während Sie Daten erfassen, während Sie die aufgelistete Monats- oder Datumsliste drehen
    for dt in self.datelist:
      d = dt.strftime("%Y%m%d")
      yyyy = d[0:4]
      mm = d[4:6]
      dd = d[6:8]

      if type=='daily':
        #Block an der Stelle, an der sich das meteorologische Observatorium befindet_nein ist eine 5-stellige Zahl
        if len(self.s_id) == 5:
          pattern = 's1'
          url = f'https://www.data.jma.go.jp/obd/stats/etrn/view/{type}_{pattern}.php?prec_no={self.pre_id}&block_no={self.s_id}&year={yyyy}&month={mm}&day={dd}&view=p1'
          out = search_data(url)
          df = (pd.DataFrame(out, columns=['Tag','Durchschnittlicher lokaler Druck','Durchschnittlicher Meeresspiegeldruck','Tag降水量','Bis zu 1 Stunde Niederschlag','Bis zu 10 Minuten Niederschlag','Durchschnittstemperatur','Höchste Temperatur','Niedrigste Temperatur','Durchschnittliche Luftfeuchtigkeit','Minimale Luftfeuchtigkeit','Durchschnittliche Windgeschwindigkeit','Maximale Windgeschwindigkeit','Maximale Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Tag照時間','Schneefall','Tiefster Schnee','Wetterübersicht (Mittag)','Wetterübersicht (Nacht)'])).assign(Jahr Monat=f'{yyyy}{mm}')
          df = df.loc[:,['Jahr Monat','Tag','Durchschnittlicher lokaler Druck','Durchschnittlicher Meeresspiegeldruck','Tag降水量','Bis zu 1 Stunde Niederschlag','Bis zu 10 Minuten Niederschlag','Durchschnittstemperatur','Höchste Temperatur','Niedrigste Temperatur','Durchschnittliche Luftfeuchtigkeit','Minimale Luftfeuchtigkeit','Durchschnittliche Windgeschwindigkeit','Maximale Windgeschwindigkeit','Maximale Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Tag照時間','Schneefall','Tiefster Schnee','Wetterübersicht (Mittag)','Wetterübersicht (Nacht)']]
          data1 = pd.concat([data1, df])
          data1.to_csv(f'{self.a_name}_{self.st_name}_{self.start}-{self.end}_{type}.csv',index=None, encoding='SJIS')
        else:
          pattern = 'a1'
          url = f'https://www.data.jma.go.jp/obd/stats/etrn/view/{type}_{pattern}.php?prec_no={self.pre_id}&block_no={self.s_id}&year={yyyy}&month={mm}&day={dd}&view=p1'
          out = search_data(url)
          df = (pd.DataFrame(out, columns=['Tag','Tag降水量','Bis zu 1 Stunde Niederschlag','Bis zu 10 Minuten Niederschlag','Durchschnittstemperatur','Höchste Temperatur','Niedrigste Temperatur','Durchschnittliche Windgeschwindigkeit','Maximale Windgeschwindigkeit','Maximale Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Die meiste Windrichtung','Tag照時間','Schneefall','Tiefster Schnee'])).assign(Jahr Monat=f'{yyyy}{mm}')
          df = df.loc[:,['Jahr Monat','Tag','Tag降水量','Bis zu 1 Stunde Niederschlag','Bis zu 10 Minuten Niederschlag','Durchschnittstemperatur','Höchste Temperatur','Niedrigste Temperatur','Durchschnittliche Windgeschwindigkeit','Maximale Windgeschwindigkeit','Maximale Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Die meiste Windrichtung','Tag照時間','Schneefall','Tiefster Schnee']]
          data1_ = pd.concat([data1_, df])
          data1_.to_csv(f'{self.a_name}_{self.st_name}_{self.start}-{self.end}_{type}.csv',index=None, encoding='SJIS')

      elif type=='hourly':
        if len(self.s_id) == 5:
          pattern = 's1'
          url = f'https://www.data.jma.go.jp/obd/stats/etrn/view/{type}_{pattern}.php?prec_no={self.pre_id}&block_no={self.s_id}&year={yyyy}&month={mm}&day={dd}&view=p1'
          out = search_data(url)
          #Datumsspalte hinzugefügt (24)
          date = pd.DataFrame((np.full([24,1], f'{yyyy}{mm}{dd}')),columns=['Datum'])
          df = pd.DataFrame(out, columns=['Zeit','Lokaler Druck','Meeresspiegeldruck','Niederschlag','Temperatur','Taupunkttemperatur','Dampfdruck','Feuchtigkeit','Windgeschwindigkeit','Windrichtung','日照Zeit間','Gesamtsonnenstrahlung','Schneefall','Schneedecke','Wetter','Wolkenvolumen','Sichtweite'])
          df = pd.concat([date, df],axis=1)
          data2 = pd.concat([data2, df])
          data2.to_csv(f'{self.a_name}_{self.st_name}_{self.start}-{self.end}_{type}.csv',index=None, encoding='SJIS')
        else:
          pattern = 'a1'
          url = f'https://www.data.jma.go.jp/obd/stats/etrn/view/{type}_{pattern}.php?prec_no={self.pre_id}&block_no={self.s_id}&year={yyyy}&month={mm}&day={dd}&view=p1'
          out = search_data(url)
          date = pd.DataFrame((np.full([24,1], f'{yyyy}{mm}{dd}')),columns=['Datum'])
          df = pd.DataFrame(out, columns=['Zeit','Niederschlag','Temperatur','Windgeschwindigkeit','Windrichtung','日照Zeit間','Schneefall','Schneedecke'])
          df = pd.concat([date, df],axis=1)
          data2_ = pd.concat([data2_, df])
          data2_.to_csv(f'{self.a_name}_{self.st_name}_{self.start}-{self.end}_{type}.csv',index=None, encoding='SJIS')

      elif type=='10min':
        if len(self.s_id) == 5:
          pattern = 's1'
          url = f'https://www.data.jma.go.jp/obd/stats/etrn/view/{type}_{pattern}.php?prec_no={self.pre_id}&block_no={self.s_id}&year={yyyy}&month={mm}&day={dd}&view=p1'
          out = search_data(url)
          #Datumsspalte hinzugefügt (6 x 24)
          date = pd.DataFrame((np.full([144,1], f'{yyyy}{mm}{dd}')),columns=['Datum'])
          df = pd.DataFrame(out, columns=['Stunde und Minute','Lokaler Druck','Meeresspiegeldruck','Niederschlag','Temperatur','Relative Luftfeuchtigkeit','Durchschnittliche Windgeschwindigkeit','Durchschnittliche Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Sonnenlichtzeit'])
          df = pd.concat([date, df],axis=1)
          data3 = pd.concat([data3, df])
          data3.to_csv(f'{self.a_name}_{self.st_name}_{self.start}-{self.end}_{type}.csv',index=None, encoding='SJIS')
        else:
          pattern = 'a1'
          url = f'https://www.data.jma.go.jp/obd/stats/etrn/view/{type}_{pattern}.php?prec_no={self.pre_id}&block_no={self.s_id}&year={yyyy}&month={mm}&day={dd}&view=p1'
          out = search_data(url)
          date = pd.DataFrame((np.full([144,1], f'{yyyy}{mm}{dd}')),columns=['Datum'])
          df = pd.DataFrame(out, columns=['Stunde und Minute','Niederschlag','Temperatur','Durchschnittliche Windgeschwindigkeit','Durchschnittliche Windrichtung','Maximale momentane Windgeschwindigkeit','Maximale momentane Windrichtung','Sonnenlichtzeit'])
          df = pd.concat([date, df],axis=1)
          data3_ = pd.concat([data3_, df])
          data3_.to_csv(f'{self.a_name}_{self.st_name}_{self.start}-{self.end}_{type}.csv',index=None, encoding='SJIS')

      print(f'{self.a_name}_{self.st_name}_{yyyy}-{mm}-{dd}_{type}')
    print(f'{self.a_name}_{self.st_name}Im{self.start}〜{self.end}von{type}Ich habe die Daten heruntergeladen.')


if __name__ == '__main__':
  a_name = input('Geben Sie die Region ein, die Sie herunterladen möchten:')
  st_name = input('Geben Sie den Speicherort ein, den Sie herunterladen möchten:')
  amedas = Get_amedas_data(a_name,st_name)
  type = input('Wählen Sie eine Zeitskala (täglich oder stündlich oder 10 Minuten):')
  if type == 'daily':
    start= input('Startmonat, den Sie erhalten möchten(yyyymm)Bitte eingeben:')
    end = input('Ende des Monats, den Sie erhalten möchten(yyyymm)Bitte eingeben:')
    amedas.set_date1(start,end)
  else:
    start= input('Startdatum, das Sie erhalten möchten(Bitte geben Sie yyyymmdd ein:')
    end = input('Enddatum, das Sie erhalten möchten(yyyymmdd)Bitte eingeben:')
    amedas.set_date2(start,end)
  
  amedas.dl_data(type)
  

Region: Tokio Punkt: Tokio Zeitskala: 10min Startdatum: 20190401 Enddatum: 20190731

Ich werde versuchen, die Daten unter der Bedingung herunterzuladen.

Der Inhalt der gespeicherten Datei "Tokyo_Tokyo_20190401-20190731_10min.csv" sieht folgendermaßen aus ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354874/10817920-dcbe-ba75-a2f8-34071b89d19d.png) # 3. Spektrumanalyse ▶ Da ich detaillierte Daten mit einer Zeitauflösung von 10 Minuten erfasst habe, werde ich die Zeitreihendaten leicht analysieren. ▶ Dieses Mal wurde das Leistungsspektrum berechnet, indem eine Spektrumanalyse auf die Temperaturschwankung während des erfassten Zeitraums angewendet wurde. Für diejenigen, die die Gefühle der Fourier-Transformation verstehen wollen, ist [dieses Video](https://www.youtube.com/watch?v=bjBZEKdlLD0&t=339s) möglicherweise leicht zu verstehen.

fft_temp.py


# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fftn, ifftn, fftfreq

class FFT:
  def __init__(self):
    df = pd.read_csv('Tokio_Tokio_20190401-20190731_10min.csv', encoding='SJIS').replace(['--','×'],-99)
    df['Temperatur'] = df['Temperatur'].astype(float)
    y = df['Temperatur'].replace(-99, (df['Temperatur'] > -99).mean()).values
    y = (y - np.mean(y)) * np.hamming(len(y))
    '''
Abtastfrequenz fs → Anzahl der pro Sekunde abgetasteten Daten d im zweiten Argument=1.0/fs
Da die Abtastfrequenz dieses Mal 10-Minuten-Intervalldaten beträgt, 1/600
    '''
    self.z = np.fft.fft(y)
    self.freq = fftfreq(len(y), d=1.0/(1/600))


  def plot(self, n_samples):
    fig, axes = plt.subplots(figsize=(8, 4), ncols=2, sharey=True)
    ax = axes[0]
    ax.plot(self.freq[1:int(n_samples/2)], abs(self.z[1:int(n_samples/2)]))
    ax.set_yscale('log')
    ax.set_xscale('log')
    ax.set_ylabel('Power')
    ax.set_xlabel('Frequenz (Hz)')
    ax = axes[1]
    ax.plot((1 / self.freq[1:int(n_samples / 2)])/(60*60), abs(self.z[1:int(n_samples / 2)]))
    ax.set_yscale('log')
    ax.set_xscale('log')
    ax.set_xlabel('Zykluszeit)')
    plt.show()
  
  def periodic_characteristics(self, n_samples):
    fft_pow_df = pd.DataFrame([(1 / self.freq[1:int(n_samples / 2)])/(60*60), np.log10(abs(self.z[1:int(n_samples / 2)]))], index=['Zykluszeit)', 'log10_power']).T
    fft_pow_df = fft_pow_df.sort_values('log10_power', ascending=False).head(10).reset_index()
    print(fft_pow_df.loc[:, ['Zykluszeit)', 'log10_power']])

if __name__ == '__main__':
  fft = FFT()
  fft.periodic_characteristics(512)
  fft.plot(512)

Der 122-Tage-Zyklus, der das größte Leistungsspektrum aufweist, ist nicht ganz richtig, aber wie erwartet war auch der 23,24-Stunden-Zyklus hervorragend, also frage ich mich, ob er passt. (Weil es die physikalischen Eigenschaften eines täglichen Zyklus hat, dass die Temperatur von morgens bis mittags steigt und abends wieder fällt) Ich würde mich freuen, wenn Sie auf Fehler hinweisen könnten.

das ist alles! !! !!

Recommended Posts