Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- def produce_wt_forecast(database='research', user='analyst', password='analyst', host='localhost',
- search_path='t152_2017_capstone2019', hoitava_osasto_nimi='KORVAKLINIKKA',
- toimenpideosasto_nimi=None, leikkaussali_nimi=None, toteutunut_toimenpide_nimi=None,
- start_y=2013, start_m=1, start_d=1, end_y=None, end_m=None, end_d=None,
- window=365, unc_int_w=0.95, hd_ps=20, w_fo=15, m_fo=15, y_fo=15, w_ps=15, m_ps=15, y_ps=15):
- """Laskee ennusteen jonotuspäivistä annettujen parametrien mukaan.
- Jonotusajan laskeminen ei ole ns. filosofisesti triviaali asia - pitää olla tulkinta siitä, miten se lasketaan.
- Tässä mallissa jonotusajan laskeminen perustuu jononpituuteen ja jonon purkamisnopeuteen.
- Nämä tiedot haetaan SQL-kyselyn avulla, jota tarkentavat parametrit annetaan funktion argumentteina.
- Jonotusaika (kun jonoonasetuspäivä on tiedossa) lasketaan siis seuraavasti:
- 1. selvitetään jononpituus kyseisenä päivänä
- 2. selvitetään (parametrien rajoituksilla) toteutuneiden leikkausten määrä / päivämäärä pitkälle tulevaisuuteen
- 3. iteroidaan päivä kerrallaan eteenpäin, ja vähennetään jononpituudesta tot. leikkausten lkm. ko. päivältä
- 4. kun jono on ehtynyt, päätellään että leikkauspäivä on koittanut.
- 5. ennuste jonotuspäivien lukumääräksi saadaan kaavallla (leikkauspäivä - jonoonasetuspäivä)
- Miksi ei ennusteta suoraan toteutuneista jonotusajoista?
- -> toteutuneissa jonotusajoissa on paljon (liikaa?) satunnaisuutta ennustamista varten
- Tätä menetelmää käyttäessä on syytä pitää mielessä, että kaikilla parametriyhdistelmillä ei malli välttämättä ole mielekäs.
- Esimerkiksi tietyllä leikkaustyypillä ei luultavasti ole omaa jonoaan, kuten hoitavalla osastolla taas todennäköisesti on.
- Ei siis välttämättä ole täysin mielekästä laskea jonotusaikaa tämän oletuksen pohjalta.
- Parameters
- ----------
- database : str
- tietokannan nimi
- user : str
- käyttäjän nimi
- password: str
- tietokannan salasana
- host : str
- host
- search_path : str
- hakupolku
- hoitava_osasto_nimi : str
- sql-kyselyn parametri: hoitavan osaston nimi (ks. tietokantataulut)
- toimenpideosasto_nimi : str
- sql-kyselyn parametri: toimenpideosaston nimi (ks. tietokantataulut)
- leikkaussali_nimi : str
- sql-kyselyn parametri: leikkaussalin nimi (ks. tietokantataulut)
- toteutunut_toimenpide_nimi : str
- sql-kyselyn parametri: toteutuneen toimenpiteen (ts. operaation) nimi (ks. tietokantataulut)
- start_y : int
- haun aloitusvuosi
- start_m : int
- haun aloituskuukausi
- start_d : int
- haun aloituspäivä
- end_y : int
- haun lopeutusvuosi
- end_m : int
- haun lopetuskuukausi
- end_d : int
- haun lopetuspäivä
- wts_df : pandas.DataFrame
- DataFrame muotoa (päivämäärä, arvioitu jonotusaika) prophet-mallin sovittamiseen
- window : int
- prophet mallin tekemän ennusteen pituus vuorokausina
- unc_int_w : float
- prophet mallin antaman luottamusvälin tarkkuus - tod.näk. että satunnainen observaatio on rajojen sisällä.
- hd_ps : int
- vapaapäivien yms. paino (holydays_prior_scale) prophet-mallissa
- w_fo : int
- viikoittaisen komponentin fourier-kertaluku prophet-mallissa
- m_fo : int
- kuukausittaisen komponentin fourier-kertaluku prophet-mallissa
- y_fo : int
- vuosittaisen komponentin fourier-kertaluku prophet-mallissa
- w_ps : int
- viikoittaisen komponentin paino (prior_scale) prophet-mallissa
- m_ps : int
- kuukausittaisen komponentin paino (prior_scale) prophet-mallissa
- y_ps : int
- vuosittaisen komponentin paino (prior_scale) prophet-mallissa
- Returns
- -------
- pandas.DataFrame
- DataFrame-objekti muotoa (päivämäärä, ennuste jonotusajaksi, luottamusvälin alaraja, luottamusvälin yläraja)
- Huom: sisältää koko opetus- ja ennusteajan.
- """
- # 1. importoinnit
- import numpy as np
- import pandas as pd
- import sklearn
- import datetime
- import re
- import psycopg2
- from fbprophet import Prophet
- # 2. apufunktiot
- def get_queue_len(date, queue_data):
- # en ihan tajua miten tämä toimii
- return queue_data[
- (queue_data[:, 0] < datetime.datetime.combine(date, (datetime.datetime.min.time()))) &
- (queue_data[:, 1] >= datetime.datetime.combine(date, (datetime.datetime.min.time())))
- ].shape[0]
- def form_where_clause(hoitava_osasto_nimi, toimenpideosasto_nimi,
- leikkaussali_nimi, toteutunut_toimenpide_nimi):
- """Muodostaa sql-kyselyssä käytettävän WHERE -lauseen argumenttien pohjalta
- Parameters
- ----------
- hoitava_osasto_nimi : str
- hoitavan osaston nimi, jos None, ei rajoita hakua
- toimenpideosasto_nimi : str
- toimenpideosaston nimi, jos None, ei rajoita hakua
- leikkaussali_nimi : str
- leikkaussalin nimi, jos None, ei rajoita hakua
- toteutunut_toimenpide_nimi : str
- toimenpiteen (ts. operaation) nimi, jos None, ei rajoita hakua
- Returns
- -------
- str
- WHERE-lause, jossa parametrit yhdistetty AND-operaattorilla
- """
- def filter_params(param_tuple):
- if param_tuple[1] != None:
- return True
- else:
- return False
- params = [('hoitava_osasto_nimi', hoitava_osasto_nimi), ('toimenpideosasto_nimi', toimenpideosasto_nimi),
- ('leikkaussali_nimi', leikkaussali_nimi), ('toteutunut_toimenpide_nimi', toteutunut_toimenpide_nimi)]
- params = list(filter(filter_params, params))
- if params == []:
- return
- where_clause = 'WHERE '
- for param in params:
- where_clause = where_clause + param[0] + ' = ' + f"'{param[1]}'" + ' AND '
- where_clause = where_clause[:-5] # poistetaan viimeinen AND
- return(where_clause)
- def connect_to_db_and_get_dts_and_ops(database, user, password, host,
- search_path, hoitava_osasto_nimi,
- toimenpideosasto_nimi,
- leikkaussali_nimi,
- toteutunut_toimenpide_nimi,
- start_y, start_m, start_d,
- end_y, end_m, end_d):
- """Yhdistää tietokantaan, hakee operaatioiden lkm / päivä ja prosessoi tiedot DataFrame-objektiksi.
- Hakua rajoitetaan antamalla hoitavan osaston, toimenpideosaston, leikkaussalin tai toimenpiteen nimi argumenttina.
- Kaikki hakuparametrit eivät välttämättä tuota mielekkäitä tuloksia.
- Oletuksena haku alkaa vuoden 2013 alusta ja päättyy hakupäivään.
- Käyttäjällä on tällöin oltava tieto siitä, onko tietokanta ajan tasalla.
- Parameters
- ----------
- database : str
- tietokannan nimi
- user : str
- käyttäjän nimi
- password: str
- tietokannan salasana
- host : str
- host
- search_path : str
- hakupolku
- hoitava_osasto_nimi : str
- hoitavan osaston nimi (ks. tietokantataulut)
- toimenpideosasto_nimi : str
- toimenpideosaston nimi (ks. tietokantataulut)
- leikkaussali_nimi : str
- leikkaussalin nimi (ks. tietokantataulut)
- toteutunut_toimenpide_nimi : str
- toteutuneen toimenpiteen (ts. operaation) nimi (ks. tietokantataulut)
- start_y : int
- haun aloitusvuosi
- start_m : int
- haun aloituskuukausi
- start_d : int
- haun aloituspäivä
- end_y : int
- haun lopeutusvuosi
- end_m : int
- haun lopetuskuukausi
- end_d : int
- haun lopetuspäivä
- Returns
- -------
- pandas.DataFrame
- DataFrame-objekti muotoa (päivämäärä, toteutuneiden operaatioiden lkm)
- """
- # haun alku- ja loppupäivät
- start_date = datetime.date(start_y, start_m, start_d)
- if end_y == end_m == end_d == None:
- end_date = datetime.date.today()
- else:
- end_date = datetime.date(end_y, end_m, end_d)
- # where-lauseen muodostaminen
- where_clause = form_where_clause(hoitava_osasto_nimi=hoitava_osasto_nimi,
- toimenpideosasto_nimi=toimenpideosasto_nimi,
- leikkaussali_nimi=leikkaussali_nimi,
- toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi)
- # yhteys tietokantaan
- con = psycopg2.connect(database=database, user=user,
- password=password, host='localhost')
- c = con.cursor()
- c.execute("SET search_path = " + search_path) # tätäkin voi olla tarpeellista muuttaa
- # sql-kysely
- c.execute("""
- SELECT (tapahtuma_aikaleima) AS date, COUNT(*) AS n_ops
- FROM mv_opera_leikkaus_toimenpide_20191104
- """ + where_clause + """
- GROUP BY tapahtuma_aikaleima
- ORDER BY date;
- """)
- # haun tulosten prosessointi
- ds_op_arr = np.array(c.fetchall())
- ds = [i[0].date() for i in ds_op_arr] # datetimesta dateksi
- ops = [i[1] for i in ds_op_arr]
- op_ds_dict = dict(zip(ds, ops)) # dictionaryksi nopeaa tiedonhakua varten
- dts = []
- ops = []
- dts_ops = []
- for i in range((end_date - start_date).days+1):
- dt = start_date + datetime.timedelta(i)
- try: # pitää nyt tehdä näin koska kaikkia avaimia ei välttämättä löydy
- dts_ops.append((dt, op_ds_dict[dt]))
- except: # jos päivämäärää ei ole dictionaryssä, määrätään operaatioiden lukumääräksi 0.
- dts_ops.append((dt, 0))
- # erotetaan päivämäärät ja operaatioiden lukumäärät omiksi listoikseen, ja tehdään niistä DataFrame
- dts = [do[0] for do in dts_ops]
- ops = [do[1] for do in dts_ops]
- dts_ops_df = pd.DataFrame(
- {'ds': dts,
- 'y': ops
- })
- return(dts_ops_df)
- def connect_to_db_and_get_dts_and_qls(database, user, password, host,
- search_path, hoitava_osasto_nimi,
- toimenpideosasto_nimi,
- leikkaussali_nimi,
- toteutunut_toimenpide_nimi,
- start_y, start_m, start_d,
- end_y, end_m, end_d):
- """Yhdistää tietokantaan, hakee jononpituuden / päivä ja prosessoi tiedot DataFrame-objektiksi.
- Hakua rajoitetaan antamalla hoitavan osaston, toimenpideosaston, leikkaussalin tai toimenpiteen nimi argumenttina.
- Kaikki hakuparametrit eivät välttämättä tuota mielekkäitä tuloksia.
- Oletuksena haku alkaa vuoden 2013 alusta ja päättyy hakupäivään.
- Käyttäjällä on tällöin oltava tieto siitä, onko tietokanta ajan tasalla.
- Parameters
- ----------
- database : str
- tietokannan nimi
- user : str
- käyttäjän nimi
- password: str
- tietokannan salasana
- host : str
- host
- search_path : str
- hakupolku
- hoitava_osasto_nimi : str
- hoitavan osaston nimi (ks. tietokantataulut)
- toimenpideosasto_nimi : str
- toimenpideosaston nimi (ks. tietokantataulut)
- leikkaussali_nimi : str
- leikkaussalin nimi (ks. tietokantataulut)
- toteutunut_toimenpide_nimi : str
- toteutuneen toimenpiteen (ts. operaation) nimi (ks. tietokantataulut)
- start_y : int
- haun aloitusvuosi
- start_m : int
- haun aloituskuukausi
- start_d : int
- haun aloituspäivä
- end_y : int
- haun lopeutusvuosi
- end_m : int
- haun lopetuskuukausi
- end_d : int
- haun lopetuspäivä
- Returns
- -------
- pandas.DataFrame
- DataFrame-objekti muotoa (päivämäärä, jonossa olevien potilaiden lkm)
- """
- # haun alku- ja loppupäivät
- start_date = datetime.date(start_y, start_m, start_d)
- if end_y == end_m == end_d == None:
- end_date = datetime.date.today()
- else:
- end_date = datetime.date(end_y, end_m, end_d)
- where_clause = form_where_clause(hoitava_osasto_nimi=hoitava_osasto_nimi,
- toimenpideosasto_nimi=toimenpideosasto_nimi,
- leikkaussali_nimi=leikkaussali_nimi,
- toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi)
- # yhteys tietokantaan
- con = psycopg2.connect(database=database, user=user,
- password='analyst', host='localhost')
- c = con.cursor()
- c.execute("SET search_path = " + search_path)
- c.execute("""
- SELECT jonoonasetuspvm, tapahtuma_aikaleima, leikkaussali_nimi, hoitava_osasto_nimi
- FROM mv_opera_leikkaus_toimenpide_20191104
- """ + where_clause + """
- GROUP BY henkilotunnus, tapahtuma_aikaleima, jonoonasetuspvm, leikkaussali_nimi, hoitava_osasto_nimi;
- """)
- queue_data = np.array(c.fetchall())
- queue_lens = []
- dts = []
- dts_qls = []
- for i in range((end_date - start_date).days+1):
- dt = start_date + datetime.timedelta(i)
- dts_qls.append((dt, get_queue_len(dt, queue_data)))
- dts = [dq[0] for dq in dts_qls]
- qls = [dq[1] for dq in dts_qls]
- dts_qls_df = pd.DataFrame(
- {'ds': dts,
- 'y': qls
- })
- return (dts_qls_df)
- def compute_waiting_time(date, qls, ops):
- """Laskee arvion jonotuspäivistä, kun jonoonasetuspäivä on annettu argumenttina
- Parameters
- ----------
- date : datetime.date
- päivämäärä, jolle halutaan laskea jonotusaika
- qls : pd.DataFrame
- jononpituudet sisältävä dataframe-objekti muotoa (päivämäärä, jononpituus)
- ops : pd.DataFrame
- toteutuneiden operaatioiden lukumäärän sisältävä dataframe-objekti muotoa (päivämäärä, operaatoiden lkm)
- Returns
- -------
- int / np.nan
- arvio jonotusajasta argumenttina annetulle päivämäärälle. jos arviota ei voi tehdä, palautusarvo on nan.
- """
- q_len = int(qls[qls['ds']==date]['y'])
- last_date = qls.iloc[-1]['ds']
- qls_dict = dict(zip(qls['ds'], qls['y']))
- ops_dict = dict(zip(ops['ds'], ops['y']))
- running_out_of_op_dates = False
- future_date = date
- while q_len > 0:
- future_date += datetime.timedelta(days=1)
- if future_date > last_date:
- running_out_of_op_dates = True
- break
- q_len = q_len - ops_dict[future_date]
- total_n_days = (future_date - date).days
- if running_out_of_op_dates:
- return(np.nan)
- else:
- return(total_n_days)
- def predict_wts(wts_df, window, unc_int_w, hd_ps,
- w_fo, m_fo, y_fo, w_ps, m_ps, y_ps):
- """Sovittaa syötteenä annetulla DataFrame-objektilla prophet-mallin ja tekee ennustuksen tulevista jonotusajoista
- Parameters
- ----------
- wts_df : pandas.DataFrame
- DataFrame muotoa (päivämäärä, arvioitu jonotusaika)
- window : int
- ennusteen pituus vuorokausina
- unc_int_w : float
- luottamusvälin tarkkuus - millä todennäköisyydellä satunnainen observaatio on rajojen sisällä.
- hd_ps : int
- vapaapäivien yms. paino (holydays_prior_scale)
- w_fo : int
- viikoittaisen komponentin fourier-kertaluku
- m_fo : int
- kuukausittaisen komponentin fourier-kertaluku
- y_fo : int
- vuosittaisen komponentin fourier-kertaluku
- w_ps : int
- viikoittaisen komponentin paino (prior_scale)
- m_ps : int
- kuukausittaisen komponentin paino (prior_scale)
- y_ps : int
- vuosittaisen komponentin paino (prior_scale)
- Returns
- -------
- pandas.DataFrame
- DataFrame-objekti muotoa (päivämäärä, ennustettu y:n arvo, luottamusvälin alaraja, luottamusvälin yläraja)
- """
- wts_df_na = wts_df[wts_df['y'].isna()] # ne tapaukset, joille jonotusaikaa ei voi laskea
- wts_df_nn = wts_df.dropna(axis=0, how='any') # ne tapaukset, joille jonotusajan voi laskea
- true_window = window + wts_df_na.shape[0] # pitää ennustaa myös tämänhetkisiä jonotusaikoja
- # prophet-mallin luominen ja sovitus
- p = Prophet(seasonality_mode='additive',
- daily_seasonality=False, weekly_seasonality=False, yearly_seasonality=False,
- holidays_prior_scale=hd_ps, interval_width=unc_int_w).add_seasonality(
- name='weekly', period=7, fourier_order=w_fo, prior_scale=w_ps).add_seasonality(
- name='monthly', period=30.5, fourier_order=m_fo, prior_scale=m_ps).add_seasonality(
- name='yearly', period=365.25, fourier_order=y_fo, prior_scale=y_ps)
- p.add_country_holidays(country_name='FI')
- p.fit(wts_df_nn)
- # ennustedataframe todellisen ennusteikkunan mukaan (sis. tällä hetkellä jonossa olevat)
- future = p.make_future_dataframe(periods=true_window)
- forecast = p.predict(future)
- return(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']])
- # 3. ennusteen tekeminen
- # haetaan päivämäärä/jononpituudet ja päivämäärä/operaatiot
- ds_qls = connect_to_db_and_get_dts_and_qls(database=database, user=user, password=password, host=host,
- search_path=search_path, hoitava_osasto_nimi=hoitava_osasto_nimi,
- toimenpideosasto_nimi=toimenpideosasto_nimi,
- leikkaussali_nimi=leikkaussali_nimi,
- toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi,
- start_y=start_y, start_m=start_m, start_d=start_d,
- end_y=end_y, end_m=end_m, end_d=end_d)
- ds_ops = connect_to_db_and_get_dts_and_ops(database=database, user=user, password=password, host=host,
- search_path=search_path, hoitava_osasto_nimi=hoitava_osasto_nimi,
- toimenpideosasto_nimi=toimenpideosasto_nimi,
- leikkaussali_nimi=leikkaussali_nimi,
- toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi,
- start_y=start_y, start_m=start_m, start_d=start_d,
- end_y=end_y, end_m=end_m, end_d=end_d)
- # lasketaan jonotusajat ja muodostetaan dataframe
- ds = ds_qls['ds'].values
- wts = [compute_waiting_time(d, ds_qls, ds_ops) for d in ds]
- ds_wts = pd.DataFrame(
- {'ds': ds,
- 'y': wts
- })
- # tehdään ennuste ja palautetaan se
- f_wts = predict_wts(wts_df=ds_wts, window=window, unc_int_w=unc_int_w, hd_ps=hd_ps,
- w_fo=w_fo, m_fo=m_fo, y_fo=y_fo, w_ps=w_ps, m_ps=m_ps, y_ps=y_ps)
- return(f_wts)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement