11import datetime
2- import locale
2+ from datetime import date
3+ from datetime import datetime
34from typing import Literal
5+
46import pendulum
57import polars as pl
6- from datetime import datetime , date
8+
79
810def monthdelta (d1 : datetime , d2 : datetime ):
911 """Finner differansen mellom to måneder"""
@@ -16,13 +18,13 @@ def parse_period(period: str) -> datetime:
1618 """Parser en periode til datetime"""
1719 return datetime .strptime (period , "%Y-%m" )
1820
21+
1922def multi_join (
2023 dfs : list [pl .DataFrame ],
2124 on : str ,
2225 how : Literal ["left" , "right" , "inner" , "cross" , "semi" , "anti" ] = "left" ,
2326):
24- """
25- Slår sammen flere Polars DataFrames sekvensielt på en spesifisert kolonne.
27+ """Slår sammen flere Polars DataFrames sekvensielt på en spesifisert kolonne.
2628
2729 Utfører en kjedet sammenslåing (join) av flere DataFrames i en gitt liste basert på én felles kolonne.
2830 Hver DataFrame legges til én etter én, med spesifisert join-type og automatisk suffiks for overlappende kolonnenavn.
@@ -35,7 +37,6 @@ def multi_join(
3537 Returns:
3638 pl.DataFrame: Ett samlet DataFrame etter sekvensiell join av alle inputtabellene.
3739 """
38-
3940 df = dfs [0 ]
4041
4142 for idx , i in enumerate (dfs [1 :]):
@@ -53,8 +54,7 @@ def __init__(
5354 internal_col : str = "avg" ,
5455 dt_out_format : str = "%b %Y" ,
5556 ) -> None :
56- """
57- Representerer en datakilde tilrettelagt for tidsbasert analyse og gruppering.
57+ """Representerer en datakilde tilrettelagt for tidsbasert analyse og gruppering.
5858
5959 Denne klassen organiserer et Polars DataFrame ved å sortere på en datokolonne
6060 og gjør det mulig å gruppere og analysere utvalgte kolonner.
@@ -75,7 +75,6 @@ def __init__(
7575 internal_col (str, valgfritt): Internt kolonnenavn brukt for aggregering. Standard er "avg".
7676 dt_out_format (str, valgfritt): Format for datoer ved visning. Standard er "%b %Y".
7777 """
78-
7978 self .data = data .sort (date_col )
8079 self ._date = date_col
8180 self ._col = col_name
@@ -84,25 +83,22 @@ def __init__(
8483 self ._dt_out_format = dt_out_format
8584
8685 def latest_date (self ) -> None | datetime :
87- """
88- Henter den siste datoen fra datakolonnen.
86+ """Henter den siste datoen fra datakolonnen.
8987
9088 Returns:
91- datetime | None: Den siste datoen som finnes i datasettet,
89+ datetime | None: Den siste datoen som finnes i datasettet,
9290 eller None dersom verdien ikke er en gyldig datetime.
9391 """
94-
9592 latest = self .data .get_column (self ._date ).last ()
9693 if isinstance (latest , datetime ):
9794 return latest
98- elif isinstance (latest , date ): # <- use date directly
95+ elif isinstance (latest , date ): # <- use date directly
9996 return datetime .combine (latest , datetime .min .time ())
10097 else :
10198 return None
10299
103100 def _percent_change (self , series_1 : pl .Expr , series_2 : pl .Expr ):
104- """
105- Beregner prosentvis endring mellom to serier.
101+ """Beregner prosentvis endring mellom to serier.
106102
107103 Args:
108104 series_1 (pl.Expr): Første uttrykk / serie.
@@ -111,25 +107,21 @@ def _percent_change(self, series_1: pl.Expr, series_2: pl.Expr):
111107 Returns:
112108 pl.Expr: Et uttrykk som representerer prosentvis endring.
113109 """
114-
115110 return ((series_1 - series_2 ) / series_2 ) * 100
116111
117112 def _create_date (self , date : datetime ) -> str :
118- """
119- Formaterer en datetime til en str med definert utdataformat.
113+ """Formaterer en datetime til en str med definert utdataformat.
120114
121115 Args:
122116 date (datetime): Datoen som skal formateres.
123117
124118 Returns:
125119 str: Formatert og kapitalisert dato som tekst.
126120 """
127-
128121 return date .strftime (self ._dt_out_format ).capitalize ()
129122
130123 def _gen_header (self , n : int , skip : int = 0 ):
131- """
132- Genererer en overskrift som viser datoperioden basert på antall perioder og hopp.
124+ """Genererer en overskrift som viser datoperioden basert på antall perioder og hopp.
133125
134126 Args:
135127 n (int): Antall måneder per periode.
@@ -138,15 +130,13 @@ def _gen_header(self, n: int, skip: int = 0):
138130 Returns:
139131 str: En str som representerer datoperioden (eks. "Jan 2023 - Mar 2023").
140132 """
141-
142133 dates = self .data .get_column (self ._date ).unique ().sort ()
143134 latest : datetime = dates [- 1 - (n * skip )]
144135 oldest : datetime = dates [- 1 - ((n * skip ) + n )]
145136 return f"{ self ._create_date (oldest )} - { self ._create_date (latest )} "
146137
147138 def _base (self , n : int , * agg , ** named_aggs ):
148- """
149- Utfører aggregering over dynamiske tidsvinduer og grupper.
139+ """Utfører aggregering over dynamiske tidsvinduer og grupper.
150140
151141 Args:
152142 n (int): Størrelsen på tidsvinduet i måneder.
@@ -166,8 +156,7 @@ def _base(self, n: int, *agg, **named_aggs):
166156 )
167157
168158 def _base_w_header (self , n : int , skip : int = 0 , * agg , ** named_aggs ):
169- """
170- Genererer en overskrift for perioden og returnerer resultatet fra baseaggregering.
159+ """Genererer en overskrift for perioden og returnerer resultatet fra baseaggregering.
171160
172161 Kombinerer datoperiode-headeren med aggregerte resultater fra `_base`.
173162
@@ -185,8 +174,7 @@ def _base_w_header(self, n: int, skip: int = 0, *agg, **named_aggs):
185174 return header , result
186175
187176 def n_month (self , n : int , skip : int = 0 ) -> tuple [str , pl .DataFrame ]:
188- """
189- Henter siste tilgjengelige verdi for gjennomsnittskolonnen for en periode.
177+ """Henter siste tilgjengelige verdi for gjennomsnittskolonnen for en periode.
190178
191179 Args:
192180 n (int): Antall måneder i perioden.
@@ -195,14 +183,12 @@ def n_month(self, n: int, skip: int = 0) -> tuple[str, pl.DataFrame]:
195183 Returns:
196184 tuple[str, pl.DataFrame]: En tuple med overskrift og filtrert datasett med siste verdi.
197185 """
198-
199186 return self ._base_w_header (
200187 n , skip , ** {self ._avg : pl .col (self ._avg ).get (- 1 - skip )}
201188 )
202189
203190 def n_month_percent (self , n : int , skip : int = 0 ) -> tuple [str , pl .DataFrame ]:
204- """
205- Beregner prosentvis endring mellom to perioder og returnerer med overskrift.
191+ """Beregner prosentvis endring mellom to perioder og returnerer med overskrift.
206192
207193 Args:
208194 n (int): Antall måneder i perioden.
@@ -211,7 +197,6 @@ def n_month_percent(self, n: int, skip: int = 0) -> tuple[str, pl.DataFrame]:
211197 Returns:
212198 tuple[str, pl.DataFrame]: En tuple med periodebeskrivelse og datasett med prosentendringer.
213199 """
214-
215200 return self ._base_w_header (
216201 n ,
217202 skip ,
@@ -223,8 +208,7 @@ def n_month_percent(self, n: int, skip: int = 0) -> tuple[str, pl.DataFrame]:
223208 )
224209
225210 def n_percent_rolling (self , n : int , skip : int = 0 ):
226- """
227- Beregner rullerende prosentvis endring over en periode og returnerer med datoperiode-header.
211+ """Beregner rullerende prosentvis endring over en periode og returnerer med datoperiode-header.
228212
229213 Denne metoden bruker en rullerende tidsvinduanalyse for å beregne prosentvis endring
230214 mellom første og siste verdi i hvert vindu, og gir samtidig en datoperiodebeskrivelse.
@@ -238,15 +222,16 @@ def n_percent_rolling(self, n: int, skip: int = 0):
238222 tuple[str, pl.DataFrame]: En tuple med en tekstlig overskrift for datoperioden og et DataFrame
239223 med prosentvis endring for hver gruppe.
240224 """
225+
241226 def _gen_header (n : int , skip : int = 0 ):
242- ''' Lager overskrift for hver perioden'''
227+ """ Lager overskrift for hver perioden"""
243228 dates = self .data .get_column (self ._date ).unique ().sort ()
244229 latest : datetime = dates [- 1 - (skip )]
245230 oldest : datetime = dates [- 1 - ((skip ) + n - 1 )]
246231 return f"{ self ._create_date (oldest )} - { self ._create_date (latest )} "
247232
248233 def map_test (x : pl .DataFrame ):
249- ''' Lager det rullende gjennomsnittet for hver periodegruppe'''
234+ """ Lager det rullende gjennomsnittet for hver periodegruppe"""
250235 if x .shape [0 ] != n :
251236 x = x .with_columns (
252237 ** {self ._avg : pl .col (self ._col ).fill_null (strategy = "backward" )}
@@ -276,8 +261,7 @@ def map_test(x: pl.DataFrame):
276261 )
277262
278263 def n_mean_rolling (self , n : int , skip : int = 0 ):
279- """
280- Beregner et rullerende gjennomsnitt for hver gruppe i datasettet og returnerer med datoperiode-header.
264+ """Beregner et rullerende gjennomsnitt for hver gruppe i datasettet og returnerer med datoperiode-header.
281265
282266 Denne metoden beregner gjennomsnittet av verdiene innenfor et rullerende vindu på `n` måneder.
283267 Hvis et vindu inneholder færre enn `n` datapunkter, blir manglende verdier fylt bakover.
@@ -293,25 +277,22 @@ def n_mean_rolling(self, n: int, skip: int = 0):
293277 """
294278
295279 def _gen_header (n : int , skip : int = 0 ):
296- ''' Lager overskrift for hver perioden'''
280+ """ Lager overskrift for hver perioden"""
297281 dates = self .data .get_column (self ._date ).unique ().sort ()
298282 latest : datetime = dates [- 1 - (skip )]
299283 oldest : datetime = dates [- 1 - ((skip ) + n - 1 )]
300284 return f"{ self ._create_date (oldest )} - { self ._create_date (latest )} "
301285
302286 def map_test (x : pl .DataFrame ):
303- ''' Lager det rullende gjennomsnittet for hver periodegruppe'''
287+ """ Lager det rullende gjennomsnittet for hver periodegruppe"""
304288 if x .shape [0 ] != n :
305289 x = x .with_columns (
306290 ** {self ._avg : pl .col (self ._col ).fill_null (strategy = "backward" )}
307291 )
308292 else :
309- x = x .with_columns (
310- ** {
311- self ._avg : pl .col (self ._col ).mean ()
312- }
313- )
293+ x = x .with_columns (** {self ._avg : pl .col (self ._col ).mean ()})
314294 return x
295+
315296 return _gen_header (n , skip ), (
316297 self .data .rolling (
317298 pl .col (self ._date ),
@@ -329,11 +310,10 @@ def map_test(x: pl.DataFrame):
329310 def n_month_rolling_percent_compare (
330311 self , n : int , skip : int = 0 , skip_1 : int = 1
331312 ) -> tuple [str , pl .DataFrame ]:
332- """
333- Sammenligner rullerende gjennomsnitt mellom to perioder og beregner prosentvis endring.
313+ """Sammenligner rullerende gjennomsnitt mellom to perioder og beregner prosentvis endring.
334314
335- Denne metoden sammenligner det rullerende gjennomsnittet for én periode mot en annen forskjøvet
336- periode, og returnerer en prosentvis endring for hver gruppe. Resultatet inkluderer også en
315+ Denne metoden sammenligner det rullerende gjennomsnittet for én periode mot en annen forskjøvet
316+ periode, og returnerer en prosentvis endring for hver gruppe. Resultatet inkluderer også en
337317 overskrift som beskriver begge periodene.
338318
339319 Args:
@@ -342,10 +322,9 @@ def n_month_rolling_percent_compare(
342322 skip_1 (int, valgfritt): Antall perioder å hoppe over for den sammenlignende perioden. Standard er 1.
343323
344324 Returns:
345- tuple[str, pl.DataFrame]: En tuple bestående av en sammensatt overskrift og et DataFrame
325+ tuple[str, pl.DataFrame]: En tuple bestående av en sammensatt overskrift og et DataFrame
346326 med prosentvis endring mellom de to periodene for hver gruppe.
347327 """
348-
349328 header_1 , series_1 = self .n_mean_rolling (n , skip )
350329 header_2 , series_2 = self .n_mean_rolling (n , skip_1 )
351330
@@ -363,8 +342,7 @@ def n_month_rolling_percent_compare(
363342 def n_month_percent_compare (
364343 self , n : int , skip : int = 0 , skip_1 : int = 1
365344 ) -> tuple [str , pl .DataFrame ]:
366- """
367- Sammenligner prosentvis endring i verdier mellom to definerte perioder.
345+ """Sammenligner prosentvis endring i verdier mellom to definerte perioder.
368346
369347 Denne metoden henter to distinkte perioder (hver på `n` måneder), basert på ulike skipverdier,
370348 og beregner den prosentvise forskjellen i verdier mellom dem for hver gruppe i datasettet.
@@ -379,12 +357,11 @@ def n_month_percent_compare(
379357 tuple[str, pl.DataFrame]: En tuple med beskrivelse av periodeparene og et DataFrame
380358 med prosentvis endring mellom verdiene for hver gruppe.
381359 """
382-
383360 header_1 , series_1 = self .n_month (n , skip )
384361 header_2 , series_2 = self .n_month (n , skip_1 )
385362
386363 header = f"{ header_2 } / { header_1 } "
387-
364+
388365 joined = series_1 .join (series_2 , on = self ._group ).select (
389366 ** {
390367 self ._group : pl .col (self ._group ),
@@ -395,28 +372,31 @@ def n_month_percent_compare(
395372 )
396373 return header , joined
397374
375+
398376def rounded_average (df : pd .DataFrame , ordered_columns : list [str ]):
399377 df_copy = df [ordered_columns ]
400378 df_copy = df_copy .round (1 )
401379 res = df_copy .sum (axis = "columns" ).div (len (ordered_columns ))
402380 return res .round (1 )
403381
382+
404383def calc_change_rate (df : pd .DataFrame , ordered_columns : list [str ], n : int = 1 ):
405384 results = []
406385 for i in range (n , len (ordered_columns ), 1 ):
407386 col_present = ordered_columns [i ]
408- previous_period = ordered_columns [i - n ]
409- chg_rate = (df [col_present ] - df [previous_period ])* 100 / df [previous_period ]
387+ previous_period = ordered_columns [i - n ]
388+ chg_rate = (df [col_present ] - df [previous_period ]) * 100 / df [previous_period ]
410389 results .append (chg_rate )
411390
412- return pd .concat (results , axis = "columns" , keys = ordered_columns [n :])
391+ return pd .concat (results , axis = "columns" , keys = ordered_columns [n :])
392+
413393
414394def rolling_change_rate (df : pd .DataFrame , step : int = 1 ):
415395 results = []
416396 for i in range (step , len (df .columns ), step ):
417397 col_present = df .columns [i ]
418- previous_period = df .columns [i - step ]
419- chg_rate = (df [col_present ] - df [previous_period ])* 100 / df [previous_period ]
398+ previous_period = df .columns [i - step ]
399+ chg_rate = (df [col_present ] - df [previous_period ]) * 100 / df [previous_period ]
420400 results .append (chg_rate )
421401
422- return pd .concat (results , axis = "columns" , keys = df .columns [step :])
402+ return pd .concat (results , axis = "columns" , keys = df .columns [step :])
0 commit comments