Построение графика, если данные доступны в любой момент времени для каждой станции, один график

#python #r #pandas #matplotlib

#питон #r #панды #matplotlib

Вопрос:

Как следует из названия, я хотел бы построить график доступности данных в любой момент времени для каждой станции. График можно рассматривать как карту или точечный график, где номер станции и время являются координатами. Который будет отображать вертикальные линии, где есть данные (т. Е. Числа с плавающей запятой / целые числа), и в качестве пробела, если данные отсутствуют (т.е. NaNs), временное разрешение — ежедневное.

Аналогично сюжету в конце поста. Который является результатом вывода пакета R, ‘Climatol’ (функция однородности).

Я хотел бы знать, существует ли аналогичный способ построения графика в PYTHON, я предпочтительно не хочу использовать пакет R, так как он делает больше, чем просто построение графика, и, следовательно, займет много часов для тысяч данных станции.

Некоторые выборочные данные (ежедневные временные ряды) каждой станции будут выглядеть следующим образом ;

 station1 = pd.DataFrame(pd.np.random.rand(100, 1)).set_index(pd.date_range(start = '2000/01/01', periods = 100))
station2 = pd.DataFrame(pd.np.random.rand(200, 1)).set_index(pd.date_range(start = '2000/03/01', periods = 200))
station3 = pd.DataFrame(pd.np.random.rand(300, 1)).set_index(pd.date_range(start = '2000/06/01', periods = 300))
station4 = pd.DataFrame(pd.np.random.rand(50, 1)).set_index(pd.date_range(start = '2000/09/01', periods = 50))
station5 = pd.DataFrame(pd.np.random.rand(340, 1)).set_index(pd.date_range(start = '2000/01/01', periods = 340))
 

Реальные выборочные данные; https://drive.google.com/drive/folders/15PwpWIh13tyOyzFUTiE9LgrxUMm-9gh6?usp=sharing
Код для открытия двух станций;

 import pandas as pd
import numpy as np


df1 = pd.read_csv('wgenf - 2019-04-17T012724.318.genform1_proc',skiprows = 8,delimiter = '  ')
df1.drop(df1.tail(6).index,inplace=True)
df1 = df1.iloc[:,[1,3]]
df1.iloc[:,1].replace('-',np.nan,inplace=True)
df1 = df1.dropna()
df1['Date(NZST)'] = pd.to_datetime(df1.iloc[:,0],format = "%Y %m %d")
df1 = df1.set_index('Date(NZST)')

df2 = pd.read_csv('wgenf - 2019-04-17T012830.116.genform1_proc',skiprows = 8,delimiter = '  ')
df2.drop(df2.tail(6).index,inplace=True)
df2 = df2.iloc[:,[1,3]]
df2.iloc[:,1].replace('-',np.nan,inplace=True)
df2 = df2.dropna()
df2['Date(NZST)'] = pd.to_datetime(df2.iloc[:,0],format = "%Y %m %d")
df2 = df2.set_index('Date(NZST)')
 

введите описание изображения здесь

Расширение кода Асмуса (ответ ниже) для нескольких станций

 import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
import glob as glob
start = '1900/01/01'
end = '2018/12/31'
counter = 0
filenames = glob.glob('data/temperature/*.genform1_proc')
for filename in filenames:
    with open(filename, newline='') as f:

        ### read the csv file with pandas, using the correct tab delimiter 
        df1 = pd.read_csv(f,skiprows = 8,delimiter = 't',)
        df1.drop(df1.tail(8).index,inplace=True)


        ### replace invalid '-' with useable np.nan (not a number)
        df1.replace('-',np.nan,inplace=True)
        df1['Date(NZST)'] = pd.to_datetime(df1['Date(NZST)'],format = "%Y %m %d")
        df1 = df1.set_index('Date(NZST)',drop=False)

        ### To make sure that we have data on all dates:
        #   create a new index, based on the old range, but daily frequency
        idx = pd.date_range(start,end,freq="D")
        df1=df1.reindex(idx, fill_value=np.nan)

        ### Make sure interesting data fields are numeric (i.e. floats)
        df1["Tmax(C)"]=pd.to_numeric(df1["Tmax(C)"])
        ### Create masks for 
        #   valid data: has both date and temperature
        valid_mask= df1['Tmax(C)'].notnull()

        ### decide where to plot the line in y space, 
        ys=[counter for v in df1['Tmax(C)'][valid_mask].values]


        plt.scatter(df1.index[valid_mask].values,ys,s=30,marker="|",color="g")
        plt.show()

        counter  =1
 

приведенный выше код в настоящее время отображает приведенный ниже.

введите описание изображения здесь

Комментарии:

1. Не уверен, что ваш образец данных является лучшим примером. Какие критерии будут указывать на доступность? Какое разрешение по времени вы хотели бы получить на своем графике? использование imshow или построение строк с чем-то подобным hlines — это первые варианты, которые приходят на ум.

2. Образец данных выводит ежедневные данные, и, следовательно, если мы смотрим только на 2000-2001 год, синим цветом будет указано, есть ли у станции данные, и ничего, если у станции отсутствуют даты. Примерные данные в точности идентичны данным, которые я привел на графике выше. Я отредактирую свой пост, чтобы было понятно, спасибо!

3. @busybear, hlines будет отображать максимальное количество данных и не будет указывать на отсутствующие данные одновременно.

4. Итак, этот график, по сути, имеет точку данных для каждого дня за 100 или около того лет? И он синий или нет, в зависимости от того, есть ли данные за этот день? Я все еще думаю, что было бы полезно иметь пример более полного набора данных.

5. Да, это правильно, вы можете думать об этом как о карте, где номер станции и время являются координатами. Я попытаюсь загрузить несколько примеров моих фактических данных с некоторыми кодами при их открытии. Приветствия

Ответ №1:

Обновлено: я обновил этот ответ в соответствии с комментариями

Итак, во-первых, ваши входные данные немного перепутаны, поскольку разделителем на самом деле является tabs ( 't' ), а первый столбец вместо этого заканчивается на , .

Важные шаги:

  • сначала позаботьтесь об очистке, заменив , на t , и, таким образом, убедитесь, что заголовки столбцов правильно считываются как df.keys() . Хотя вы можете подумать, что это не важно, постарайтесь поддерживать чистоту! 🙂
  • столбец индекса ‘Date (NZST)’ сохраняется как столбец, и создается новый столбец индекса ( idx ), который содержит все дни в заданном диапазоне, поскольку в исходных данных отсутствуют несколько дней.
  • убедитесь, что соответствующие ключи / столбцы имеют соответствующий тип, например, ‘Tmax(C)’ должен быть с плавающей точкой.
  • наконец, вы можете использовать .notnull() для получения только достоверных данных, но убедитесь, что присутствуют и дата, и температура! Это сохраняется как valid_mask для удобства использования

В конце концов, я нанес данные на график, используя зеленые вертикальные линии в качестве маркеров для «правильных» измерений и такие же красные линии для неверных данных. См. рисунок. Теперь вам нужно только запустить это для всех станций. Надеюсь, это поможет!

пример графика допустимых / недопустимых данных

 import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
from io import StringIO
import re
fpath='./wgenf - 2019-04-17T012537.711.genform1_proc'

### cleanup the input file
for_pd = StringIO()
with open(fpath) as fi:
    for line in fi:
        new_line = re.sub(r',', 't', line.rstrip(),)
        print (new_line, file=for_pd)

for_pd.seek(0)

### read the csv file with pandas, using the correct tab delimiter 
df1 = pd.read_csv(for_pd,skiprows = 8,delimiter = 't',)
df1.drop(df1.tail(6).index,inplace=True)

### replace invalid '-' with useable np.nan (not a number)
df1.replace('-',np.nan,inplace=True)
df1['Date(NZST)'] = pd.to_datetime(df1['Date(NZST)'],format = "%Y %m %d")
df1 = df1.set_index('Date(NZST)',drop=False)

### To make sure that we have data on all dates:
#   create a new index, based on the old range, but daily frequency
idx = pd.date_range(df1.index.min(), df1.index.max(),freq="D")
df1=df1.reindex(idx, fill_value=np.nan)

### Make sure interesting data fields are numeric (i.e. floats)
df1["Tmax(C)"]=pd.to_numeric(df1["Tmax(C)"])
df1["Station"]=pd.to_numeric(df1["Station"])

### Create masks for 
#   invalid data: has no date, or no temperature
#   valid data: has both date and temperature
valid_mask=( (df1['Date(NZST)'].notnull()) amp; (df1['Tmax(C)'].notnull()))
na_mask=( (df1['Date(NZST)'].isnull()) amp; (df1['Tmax(C)'].isnull()))


### Make the plot
fig,ax=plt.subplots()

### decide where to plot the line in y space, here: "1"
ys=[1 for v in df1['Station'][valid_mask].values]
### and plot the data, using a green, vertical line as marker
ax.scatter(df1.index[valid_mask].values,ys,s=10**2,marker="|",color="g")

### potentially: also plot the missing data, using a re, vertical line as marker at y=0.9
yerr=[0.9 for v in df1['Station'][na_mask].values]
ax.scatter(df1.index[na_mask].values,yerr,s=10**2,marker="|",color="r")

### set some limits on the y-axis
ax.set_ylim(0,2)

plt.show()
 

Комментарии:

1. Спасибо, что уделили немного своего времени, и да, вы читаете это нормально, однако вам не нужно беспокоиться о том, что в первом столбце есть другой разделитель, в этом случае он не понадобится. Приведенный выше код будет считывать данные просто отлично (по крайней мере, для меня), и вы можете просто работать над этим. Кроме того, выбранный диапазон «2000-2001» был всего лишь примером, я на самом деле пытаюсь заставить его пройти через гораздо больший диапазон, т.Е. 1900-2018, где каждая станция будет находиться между этим временным диапазоном, но не обязательно в одно и то же время или длиной. @Asmus

2. Кроме того, я никогда не слышал о диаграммах Ганта и функции axvline(), еще раз спасибо за ваш вклад, я вижу, что вы пытаетесь сделать, я тоже попробую попробовать. Приветствия

3. @WDS Я обновил свой ответ, посмотрим, имеет ли это больше смысла 🙂

4. @Amus, это действительно здорово. Спасибо! Теперь я просто пытаюсь сделать все это для каждой станции на одном графике. В настоящее время я использую glob для перебора всех файлов, но, похоже, я не могу понять, как сделать так, чтобы каждый файл / станция имела разные значения по оси Y. В настоящее время у меня есть следующий код, пытающийся создать счетчик, он находится в самом конце сообщения.

5. Оказывается, когда я модифицировал ваш код, хвостовая часть данных все еще считывала «грязные данные» в конце. Теперь это исправлено. Еще раз спасибо за ваше время!