Advertisement
Guest User

produce_wt_forecast_new

a guest
Feb 24th, 2020
264
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.24 KB | None | 0 0
  1. def produce_wt_forecast(database='research', user='analyst', password='analyst', host='localhost',
  2. search_path='t152_2017_capstone2019', hoitava_osasto_nimi='KORVAKLINIKKA',
  3. toimenpideosasto_nimi=None, leikkaussali_nimi=None, toteutunut_toimenpide_nimi=None,
  4. start_y=2013, start_m=1, start_d=1, end_y=None, end_m=None, end_d=None,
  5. 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):
  6.  
  7. """Laskee ennusteen jonotuspäivistä annettujen parametrien mukaan.
  8. Jonotusajan laskeminen ei ole ns. filosofisesti triviaali asia - pitää olla tulkinta siitä, miten se lasketaan.
  9. Tässä mallissa jonotusajan laskeminen perustuu jononpituuteen ja jonon purkamisnopeuteen.
  10. Nämä tiedot haetaan SQL-kyselyn avulla, jota tarkentavat parametrit annetaan funktion argumentteina.
  11. Jonotusaika (kun jonoonasetuspäivä on tiedossa) lasketaan siis seuraavasti:
  12. 1. selvitetään jononpituus kyseisenä päivänä
  13. 2. selvitetään (parametrien rajoituksilla) toteutuneiden leikkausten määrä / päivämäärä pitkälle tulevaisuuteen
  14. 3. iteroidaan päivä kerrallaan eteenpäin, ja vähennetään jononpituudesta tot. leikkausten lkm. ko. päivältä
  15. 4. kun jono on ehtynyt, päätellään että leikkauspäivä on koittanut.
  16. 5. ennuste jonotuspäivien lukumääräksi saadaan kaavallla (leikkauspäivä - jonoonasetuspäivä)
  17.  
  18. Miksi ei ennusteta suoraan toteutuneista jonotusajoista?
  19. -> toteutuneissa jonotusajoissa on paljon (liikaa?) satunnaisuutta ennustamista varten
  20.  
  21. Tätä menetelmää käyttäessä on syytä pitää mielessä, että kaikilla parametriyhdistelmillä ei malli välttämättä ole mielekäs.
  22. Esimerkiksi tietyllä leikkaustyypillä ei luultavasti ole omaa jonoaan, kuten hoitavalla osastolla taas todennäköisesti on.
  23. Ei siis välttämättä ole täysin mielekästä laskea jonotusaikaa tämän oletuksen pohjalta.
  24.  
  25. Parameters
  26. ----------
  27. database : str
  28. tietokannan nimi
  29. user : str
  30. käyttäjän nimi
  31. password: str
  32. tietokannan salasana
  33. host : str
  34. host
  35. search_path : str
  36. hakupolku
  37. hoitava_osasto_nimi : str
  38. sql-kyselyn parametri: hoitavan osaston nimi (ks. tietokantataulut)
  39. toimenpideosasto_nimi : str
  40. sql-kyselyn parametri: toimenpideosaston nimi (ks. tietokantataulut)
  41. leikkaussali_nimi : str
  42. sql-kyselyn parametri: leikkaussalin nimi (ks. tietokantataulut)
  43. toteutunut_toimenpide_nimi : str
  44. sql-kyselyn parametri: toteutuneen toimenpiteen (ts. operaation) nimi (ks. tietokantataulut)
  45. start_y : int
  46. haun aloitusvuosi
  47. start_m : int
  48. haun aloituskuukausi
  49. start_d : int
  50. haun aloituspäivä
  51. end_y : int
  52. haun lopeutusvuosi
  53. end_m : int
  54. haun lopetuskuukausi
  55. end_d : int
  56. haun lopetuspäivä
  57. wts_df : pandas.DataFrame
  58. DataFrame muotoa (päivämäärä, arvioitu jonotusaika) prophet-mallin sovittamiseen
  59. window : int
  60. prophet mallin tekemän ennusteen pituus vuorokausina
  61. unc_int_w : float
  62. prophet mallin antaman luottamusvälin tarkkuus - tod.näk. että satunnainen observaatio on rajojen sisällä.
  63. hd_ps : int
  64. vapaapäivien yms. paino (holydays_prior_scale) prophet-mallissa
  65. w_fo : int
  66. viikoittaisen komponentin fourier-kertaluku prophet-mallissa
  67. m_fo : int
  68. kuukausittaisen komponentin fourier-kertaluku prophet-mallissa
  69. y_fo : int
  70. vuosittaisen komponentin fourier-kertaluku prophet-mallissa
  71. w_ps : int
  72. viikoittaisen komponentin paino (prior_scale) prophet-mallissa
  73. m_ps : int
  74. kuukausittaisen komponentin paino (prior_scale) prophet-mallissa
  75. y_ps : int
  76. vuosittaisen komponentin paino (prior_scale) prophet-mallissa
  77. Returns
  78. -------
  79. pandas.DataFrame
  80. DataFrame-objekti muotoa (päivämäärä, ennuste jonotusajaksi, luottamusvälin alaraja, luottamusvälin yläraja)
  81. Huom: sisältää koko opetus- ja ennusteajan.
  82. """
  83.  
  84. # 1. importoinnit
  85. import numpy as np
  86. import pandas as pd
  87. import sklearn
  88. import datetime
  89. import re
  90. import psycopg2
  91. from fbprophet import Prophet
  92.  
  93. # 2. apufunktiot
  94. def get_queue_len(date, queue_data):
  95. # en ihan tajua miten tämä toimii
  96. return queue_data[
  97. (queue_data[:, 0] < datetime.datetime.combine(date, (datetime.datetime.min.time()))) &
  98. (queue_data[:, 1] >= datetime.datetime.combine(date, (datetime.datetime.min.time())))
  99. ].shape[0]
  100.  
  101. def form_where_clause(hoitava_osasto_nimi, toimenpideosasto_nimi,
  102. leikkaussali_nimi, toteutunut_toimenpide_nimi):
  103.  
  104. """Muodostaa sql-kyselyssä käytettävän WHERE -lauseen argumenttien pohjalta
  105.  
  106. Parameters
  107. ----------
  108. hoitava_osasto_nimi : str
  109. hoitavan osaston nimi, jos None, ei rajoita hakua
  110. toimenpideosasto_nimi : str
  111. toimenpideosaston nimi, jos None, ei rajoita hakua
  112. leikkaussali_nimi : str
  113. leikkaussalin nimi, jos None, ei rajoita hakua
  114. toteutunut_toimenpide_nimi : str
  115. toimenpiteen (ts. operaation) nimi, jos None, ei rajoita hakua
  116.  
  117. Returns
  118. -------
  119. str
  120. WHERE-lause, jossa parametrit yhdistetty AND-operaattorilla
  121. """
  122.  
  123. def filter_params(param_tuple):
  124. if param_tuple[1] != None:
  125. return True
  126. else:
  127. return False
  128.  
  129. params = [('hoitava_osasto_nimi', hoitava_osasto_nimi), ('toimenpideosasto_nimi', toimenpideosasto_nimi),
  130. ('leikkaussali_nimi', leikkaussali_nimi), ('toteutunut_toimenpide_nimi', toteutunut_toimenpide_nimi)]
  131.  
  132. params = list(filter(filter_params, params))
  133. if params == []:
  134. return
  135. where_clause = 'WHERE '
  136. for param in params:
  137. where_clause = where_clause + param[0] + ' = ' + f"'{param[1]}'" + ' AND '
  138. where_clause = where_clause[:-5] # poistetaan viimeinen AND
  139. return(where_clause)
  140.  
  141. def connect_to_db_and_get_dts_and_ops(database, user, password, host,
  142. search_path, hoitava_osasto_nimi,
  143. toimenpideosasto_nimi,
  144. leikkaussali_nimi,
  145. toteutunut_toimenpide_nimi,
  146. start_y, start_m, start_d,
  147. end_y, end_m, end_d):
  148.  
  149. """Yhdistää tietokantaan, hakee operaatioiden lkm / päivä ja prosessoi tiedot DataFrame-objektiksi.
  150. Hakua rajoitetaan antamalla hoitavan osaston, toimenpideosaston, leikkaussalin tai toimenpiteen nimi argumenttina.
  151. Kaikki hakuparametrit eivät välttämättä tuota mielekkäitä tuloksia.
  152. Oletuksena haku alkaa vuoden 2013 alusta ja päättyy hakupäivään.
  153. Käyttäjällä on tällöin oltava tieto siitä, onko tietokanta ajan tasalla.
  154.  
  155. Parameters
  156. ----------
  157. database : str
  158. tietokannan nimi
  159. user : str
  160. käyttäjän nimi
  161. password: str
  162. tietokannan salasana
  163. host : str
  164. host
  165. search_path : str
  166. hakupolku
  167. hoitava_osasto_nimi : str
  168. hoitavan osaston nimi (ks. tietokantataulut)
  169. toimenpideosasto_nimi : str
  170. toimenpideosaston nimi (ks. tietokantataulut)
  171. leikkaussali_nimi : str
  172. leikkaussalin nimi (ks. tietokantataulut)
  173. toteutunut_toimenpide_nimi : str
  174. toteutuneen toimenpiteen (ts. operaation) nimi (ks. tietokantataulut)
  175. start_y : int
  176. haun aloitusvuosi
  177. start_m : int
  178. haun aloituskuukausi
  179. start_d : int
  180. haun aloituspäivä
  181. end_y : int
  182. haun lopeutusvuosi
  183. end_m : int
  184. haun lopetuskuukausi
  185. end_d : int
  186. haun lopetuspäivä
  187.  
  188. Returns
  189. -------
  190. pandas.DataFrame
  191. DataFrame-objekti muotoa (päivämäärä, toteutuneiden operaatioiden lkm)
  192. """
  193.  
  194.  
  195. # haun alku- ja loppupäivät
  196. start_date = datetime.date(start_y, start_m, start_d)
  197. if end_y == end_m == end_d == None:
  198. end_date = datetime.date.today()
  199. else:
  200. end_date = datetime.date(end_y, end_m, end_d)
  201.  
  202. # where-lauseen muodostaminen
  203. where_clause = form_where_clause(hoitava_osasto_nimi=hoitava_osasto_nimi,
  204. toimenpideosasto_nimi=toimenpideosasto_nimi,
  205. leikkaussali_nimi=leikkaussali_nimi,
  206. toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi)
  207.  
  208. # yhteys tietokantaan
  209. con = psycopg2.connect(database=database, user=user,
  210. password=password, host='localhost')
  211. c = con.cursor()
  212.  
  213. c.execute("SET search_path = " + search_path) # tätäkin voi olla tarpeellista muuttaa
  214.  
  215. # sql-kysely
  216. c.execute("""
  217. SELECT (tapahtuma_aikaleima) AS date, COUNT(*) AS n_ops
  218. FROM mv_opera_leikkaus_toimenpide_20191104
  219. """ + where_clause + """
  220. GROUP BY tapahtuma_aikaleima
  221. ORDER BY date;
  222. """)
  223.  
  224. # haun tulosten prosessointi
  225. ds_op_arr = np.array(c.fetchall())
  226.  
  227. ds = [i[0].date() for i in ds_op_arr] # datetimesta dateksi
  228. ops = [i[1] for i in ds_op_arr]
  229.  
  230. op_ds_dict = dict(zip(ds, ops)) # dictionaryksi nopeaa tiedonhakua varten
  231.  
  232. dts = []
  233. ops = []
  234.  
  235. dts_ops = []
  236.  
  237. for i in range((end_date - start_date).days+1):
  238. dt = start_date + datetime.timedelta(i)
  239. try: # pitää nyt tehdä näin koska kaikkia avaimia ei välttämättä löydy
  240. dts_ops.append((dt, op_ds_dict[dt]))
  241. except: # jos päivämäärää ei ole dictionaryssä, määrätään operaatioiden lukumääräksi 0.
  242. dts_ops.append((dt, 0))
  243.  
  244. # erotetaan päivämäärät ja operaatioiden lukumäärät omiksi listoikseen, ja tehdään niistä DataFrame
  245. dts = [do[0] for do in dts_ops]
  246. ops = [do[1] for do in dts_ops]
  247.  
  248.  
  249. dts_ops_df = pd.DataFrame(
  250. {'ds': dts,
  251. 'y': ops
  252. })
  253.  
  254. return(dts_ops_df)
  255.  
  256. def connect_to_db_and_get_dts_and_qls(database, user, password, host,
  257. search_path, hoitava_osasto_nimi,
  258. toimenpideosasto_nimi,
  259. leikkaussali_nimi,
  260. toteutunut_toimenpide_nimi,
  261. start_y, start_m, start_d,
  262. end_y, end_m, end_d):
  263.  
  264. """Yhdistää tietokantaan, hakee jononpituuden / päivä ja prosessoi tiedot DataFrame-objektiksi.
  265. Hakua rajoitetaan antamalla hoitavan osaston, toimenpideosaston, leikkaussalin tai toimenpiteen nimi argumenttina.
  266. Kaikki hakuparametrit eivät välttämättä tuota mielekkäitä tuloksia.
  267. Oletuksena haku alkaa vuoden 2013 alusta ja päättyy hakupäivään.
  268. Käyttäjällä on tällöin oltava tieto siitä, onko tietokanta ajan tasalla.
  269.  
  270. Parameters
  271. ----------
  272. database : str
  273. tietokannan nimi
  274. user : str
  275. käyttäjän nimi
  276. password: str
  277. tietokannan salasana
  278. host : str
  279. host
  280. search_path : str
  281. hakupolku
  282. hoitava_osasto_nimi : str
  283. hoitavan osaston nimi (ks. tietokantataulut)
  284. toimenpideosasto_nimi : str
  285. toimenpideosaston nimi (ks. tietokantataulut)
  286. leikkaussali_nimi : str
  287. leikkaussalin nimi (ks. tietokantataulut)
  288. toteutunut_toimenpide_nimi : str
  289. toteutuneen toimenpiteen (ts. operaation) nimi (ks. tietokantataulut)
  290. start_y : int
  291. haun aloitusvuosi
  292. start_m : int
  293. haun aloituskuukausi
  294. start_d : int
  295. haun aloituspäivä
  296. end_y : int
  297. haun lopeutusvuosi
  298. end_m : int
  299. haun lopetuskuukausi
  300. end_d : int
  301. haun lopetuspäivä
  302.  
  303. Returns
  304. -------
  305. pandas.DataFrame
  306. DataFrame-objekti muotoa (päivämäärä, jonossa olevien potilaiden lkm)
  307. """
  308.  
  309. # haun alku- ja loppupäivät
  310. start_date = datetime.date(start_y, start_m, start_d)
  311. if end_y == end_m == end_d == None:
  312. end_date = datetime.date.today()
  313. else:
  314. end_date = datetime.date(end_y, end_m, end_d)
  315.  
  316. where_clause = form_where_clause(hoitava_osasto_nimi=hoitava_osasto_nimi,
  317. toimenpideosasto_nimi=toimenpideosasto_nimi,
  318. leikkaussali_nimi=leikkaussali_nimi,
  319. toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi)
  320.  
  321. # yhteys tietokantaan
  322. con = psycopg2.connect(database=database, user=user,
  323. password='analyst', host='localhost')
  324. c = con.cursor()
  325.  
  326. c.execute("SET search_path = " + search_path)
  327.  
  328. c.execute("""
  329. SELECT jonoonasetuspvm, tapahtuma_aikaleima, leikkaussali_nimi, hoitava_osasto_nimi
  330. FROM mv_opera_leikkaus_toimenpide_20191104
  331. """ + where_clause + """
  332. GROUP BY henkilotunnus, tapahtuma_aikaleima, jonoonasetuspvm, leikkaussali_nimi, hoitava_osasto_nimi;
  333. """)
  334. queue_data = np.array(c.fetchall())
  335.  
  336. queue_lens = []
  337. dts = []
  338.  
  339. dts_qls = []
  340.  
  341. for i in range((end_date - start_date).days+1):
  342. dt = start_date + datetime.timedelta(i)
  343.  
  344. dts_qls.append((dt, get_queue_len(dt, queue_data)))
  345.  
  346. dts = [dq[0] for dq in dts_qls]
  347. qls = [dq[1] for dq in dts_qls]
  348.  
  349. dts_qls_df = pd.DataFrame(
  350. {'ds': dts,
  351. 'y': qls
  352. })
  353.  
  354. return (dts_qls_df)
  355.  
  356. def compute_waiting_time(date, qls, ops):
  357.  
  358. """Laskee arvion jonotuspäivistä, kun jonoonasetuspäivä on annettu argumenttina
  359.  
  360. Parameters
  361. ----------
  362. date : datetime.date
  363. päivämäärä, jolle halutaan laskea jonotusaika
  364. qls : pd.DataFrame
  365. jononpituudet sisältävä dataframe-objekti muotoa (päivämäärä, jononpituus)
  366. ops : pd.DataFrame
  367. toteutuneiden operaatioiden lukumäärän sisältävä dataframe-objekti muotoa (päivämäärä, operaatoiden lkm)
  368.  
  369. Returns
  370. -------
  371. int / np.nan
  372. arvio jonotusajasta argumenttina annetulle päivämäärälle. jos arviota ei voi tehdä, palautusarvo on nan.
  373. """
  374.  
  375. q_len = int(qls[qls['ds']==date]['y'])
  376.  
  377. last_date = qls.iloc[-1]['ds']
  378.  
  379. qls_dict = dict(zip(qls['ds'], qls['y']))
  380. ops_dict = dict(zip(ops['ds'], ops['y']))
  381.  
  382. running_out_of_op_dates = False
  383.  
  384. future_date = date
  385.  
  386. while q_len > 0:
  387. future_date += datetime.timedelta(days=1)
  388. if future_date > last_date:
  389. running_out_of_op_dates = True
  390. break
  391. q_len = q_len - ops_dict[future_date]
  392. total_n_days = (future_date - date).days
  393. if running_out_of_op_dates:
  394. return(np.nan)
  395. else:
  396. return(total_n_days)
  397.  
  398. def predict_wts(wts_df, window, unc_int_w, hd_ps,
  399. w_fo, m_fo, y_fo, w_ps, m_ps, y_ps):
  400.  
  401. """Sovittaa syötteenä annetulla DataFrame-objektilla prophet-mallin ja tekee ennustuksen tulevista jonotusajoista
  402.  
  403. Parameters
  404. ----------
  405. wts_df : pandas.DataFrame
  406. DataFrame muotoa (päivämäärä, arvioitu jonotusaika)
  407. window : int
  408. ennusteen pituus vuorokausina
  409. unc_int_w : float
  410. luottamusvälin tarkkuus - millä todennäköisyydellä satunnainen observaatio on rajojen sisällä.
  411. hd_ps : int
  412. vapaapäivien yms. paino (holydays_prior_scale)
  413. w_fo : int
  414. viikoittaisen komponentin fourier-kertaluku
  415. m_fo : int
  416. kuukausittaisen komponentin fourier-kertaluku
  417. y_fo : int
  418. vuosittaisen komponentin fourier-kertaluku
  419. w_ps : int
  420. viikoittaisen komponentin paino (prior_scale)
  421. m_ps : int
  422. kuukausittaisen komponentin paino (prior_scale)
  423. y_ps : int
  424. vuosittaisen komponentin paino (prior_scale)
  425.  
  426. Returns
  427. -------
  428. pandas.DataFrame
  429. DataFrame-objekti muotoa (päivämäärä, ennustettu y:n arvo, luottamusvälin alaraja, luottamusvälin yläraja)
  430. """
  431.  
  432.  
  433. wts_df_na = wts_df[wts_df['y'].isna()] # ne tapaukset, joille jonotusaikaa ei voi laskea
  434. wts_df_nn = wts_df.dropna(axis=0, how='any') # ne tapaukset, joille jonotusajan voi laskea
  435.  
  436. true_window = window + wts_df_na.shape[0] # pitää ennustaa myös tämänhetkisiä jonotusaikoja
  437.  
  438. # prophet-mallin luominen ja sovitus
  439. p = Prophet(seasonality_mode='additive',
  440. daily_seasonality=False, weekly_seasonality=False, yearly_seasonality=False,
  441. holidays_prior_scale=hd_ps, interval_width=unc_int_w).add_seasonality(
  442. name='weekly', period=7, fourier_order=w_fo, prior_scale=w_ps).add_seasonality(
  443. name='monthly', period=30.5, fourier_order=m_fo, prior_scale=m_ps).add_seasonality(
  444. name='yearly', period=365.25, fourier_order=y_fo, prior_scale=y_ps)
  445.  
  446. p.add_country_holidays(country_name='FI')
  447.  
  448. p.fit(wts_df_nn)
  449.  
  450. # ennustedataframe todellisen ennusteikkunan mukaan (sis. tällä hetkellä jonossa olevat)
  451. future = p.make_future_dataframe(periods=true_window)
  452.  
  453. forecast = p.predict(future)
  454.  
  455. return(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']])
  456.  
  457. # 3. ennusteen tekeminen
  458.  
  459. # haetaan päivämäärä/jononpituudet ja päivämäärä/operaatiot
  460. ds_qls = connect_to_db_and_get_dts_and_qls(database=database, user=user, password=password, host=host,
  461. search_path=search_path, hoitava_osasto_nimi=hoitava_osasto_nimi,
  462. toimenpideosasto_nimi=toimenpideosasto_nimi,
  463. leikkaussali_nimi=leikkaussali_nimi,
  464. toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi,
  465. start_y=start_y, start_m=start_m, start_d=start_d,
  466. end_y=end_y, end_m=end_m, end_d=end_d)
  467. ds_ops = connect_to_db_and_get_dts_and_ops(database=database, user=user, password=password, host=host,
  468. search_path=search_path, hoitava_osasto_nimi=hoitava_osasto_nimi,
  469. toimenpideosasto_nimi=toimenpideosasto_nimi,
  470. leikkaussali_nimi=leikkaussali_nimi,
  471. toteutunut_toimenpide_nimi=toteutunut_toimenpide_nimi,
  472. start_y=start_y, start_m=start_m, start_d=start_d,
  473. end_y=end_y, end_m=end_m, end_d=end_d)
  474.  
  475. # lasketaan jonotusajat ja muodostetaan dataframe
  476. ds = ds_qls['ds'].values
  477. wts = [compute_waiting_time(d, ds_qls, ds_ops) for d in ds]
  478.  
  479. ds_wts = pd.DataFrame(
  480. {'ds': ds,
  481. 'y': wts
  482. })
  483.  
  484. # tehdään ennuste ja palautetaan se
  485. f_wts = predict_wts(wts_df=ds_wts, window=window, unc_int_w=unc_int_w, hd_ps=hd_ps,
  486. w_fo=w_fo, m_fo=m_fo, y_fo=y_fo, w_ps=w_ps, m_ps=m_ps, y_ps=y_ps)
  487.  
  488. return(f_wts)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement