-
Notifications
You must be signed in to change notification settings - Fork 5
chore: use uv and ruff for analytics reporting (#4934) #4936
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
ca44d44
chore: migrate analytics package to pyproject.toml and uv (#4934)
hunterckx 2eff185
chore: fix ruff lint violations in the analytics package (#4934)
hunterckx 5bf0d14
style: apply ruff format to the analytics package (#4934)
hunterckx ab88ab0
ci: run ruff against the analytics package (#4934)
hunterckx 1cf052f
docs: update analytics setup instructions for uv (#4934)
hunterckx b3940a3
chore: add npm scripts to run ruff (#4934)
hunterckx a2c8904
chore: remove obsolete venv folder from gitignore (#4934)
hunterckx 3de99f1
ci: run ruff checks before installing all dependencies for import che…
hunterckx 0f4390d
refactor: simplify dict construction previously done via zip (#4934)
hunterckx 4c1e491
chore: make non-package status explicit and remove unnecessary ruff e…
hunterckx 8410fdd
chore: refine uv/ruff commands and add `check-format:python` script (…
hunterckx f66798d
docs: note npm scripts in readme (#4934)
hunterckx 2e84707
revert: "chore: remove obsolete venv folder from gitignore (#4934)"
hunterckx 89e9ae1
chore: add additional flags to ruff npm scripts and focus on npm scri…
hunterckx 50cf7c0
docs: add note about order of checks to workflow (#4934)
hunterckx 0efac16
chore: run ruff checks in precommit hook if uv is installed (#4934)
hunterckx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.12 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,28 @@ | ||
| import datetime as dt | ||
| from . import api as ga | ||
| from .entities import ADDITIONAL_DATA_BEHAVIOR | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
|
|
||
| from . import api as ga | ||
| from .entities import ADDITIONAL_DATA_BEHAVIOR | ||
|
|
||
|
|
||
| def get_data_df(metrics, dimensions, percentage_metrics=None, percentage_suffix="_percentage", num_keep_dimensions=None, df_processor=None, **other_params): | ||
| def get_data_df( | ||
| metrics, | ||
| dimensions, | ||
| percentage_metrics=None, | ||
| percentage_suffix="_percentage", | ||
| num_keep_dimensions=None, | ||
| df_processor=None, | ||
| **other_params, | ||
| ): | ||
| if metrics is None: | ||
| df = pd.DataFrame() | ||
| else: | ||
| df = ga.get_metrics_by_dimensions(metrics, dimensions, **other_params) | ||
|
|
||
| if dimensions: | ||
| if len(dimensions) > 1 and not num_keep_dimensions is None: | ||
| if len(dimensions) > 1 and num_keep_dimensions is not None: | ||
| df.drop(columns=dimensions[num_keep_dimensions:], inplace=True) | ||
| df.set_index(dimensions[:num_keep_dimensions], inplace=True) | ||
| for metric in metrics: | ||
|
|
@@ -22,32 +32,40 @@ def get_data_df(metrics, dimensions, percentage_metrics=None, percentage_suffix= | |
| except ValueError: | ||
| num_column = str_column.astype(float) | ||
| df[metric] = num_column | ||
|
|
||
| if percentage_metrics: | ||
| for metric in percentage_metrics: | ||
| df.insert(list(df.columns).index(metric) + 1, metric + percentage_suffix, df[metric] / df[metric].sum() * 100) | ||
|
|
||
| df.insert( | ||
| list(df.columns).index(metric) + 1, | ||
| metric + percentage_suffix, | ||
| df[metric] / df[metric].sum() * 100, | ||
| ) | ||
|
|
||
| if df_processor: | ||
| df = df_processor(df) | ||
|
|
||
| return df | ||
|
|
||
|
|
||
| def strings_to_lists(*vals): | ||
| return [[v] if isinstance(v, str) else v for v in vals] | ||
|
|
||
|
|
||
| def get_df_over_time(xlabels, metrics, dimensions, df_filter=None, **other_params): | ||
| xlabels, metrics = strings_to_lists(xlabels, metrics) | ||
|
|
||
| df = get_data_df(metrics, dimensions, **other_params) | ||
|
|
||
| # Convert date to datetime object | ||
| df.index = pd.to_datetime(df.index) | ||
|
|
||
| if (not df_filter is None): | ||
| if df_filter is not None: | ||
| df = df_filter(df) | ||
|
|
||
| # Rename for display | ||
| df.rename(columns={name: xlabels[i] for i, name in enumerate(df.columns)}, inplace=True) | ||
| df.rename( | ||
| columns={name: xlabels[i] for i, name in enumerate(df.columns)}, inplace=True | ||
| ) | ||
|
|
||
| return df | ||
|
|
||
|
|
@@ -59,27 +77,32 @@ def get_data_df_from_fields(metrics, dimensions, **other_params): | |
| :param metrics: the metrics to get | ||
| :param dimensions: the dimensions to get | ||
| :param other_params: any other parameters to be passed to the get_data_df function, including service params | ||
| :return: a DataFrame with the data from the Analytics API. | ||
| The DF has an arbitrary RangeIndex, | ||
| string columns containing dimensions with names equal to the dimension alias value, | ||
| :return: a DataFrame with the data from the Analytics API. | ||
| The DF has an arbitrary RangeIndex, | ||
| string columns containing dimensions with names equal to the dimension alias value, | ||
| and int columns containing metrics with names equal to the metric alias value. | ||
| """ | ||
| df = get_data_df( | ||
| [metric["id"] for metric in metrics], | ||
| [dimension["id"] for dimension in dimensions], | ||
| **other_params | ||
| **other_params, | ||
| ) | ||
| return df.reset_index().rename(columns=get_rename_dict(dimensions+metrics)).copy() | ||
| return df.reset_index().rename(columns=get_rename_dict(dimensions + metrics)).copy() | ||
|
|
||
|
|
||
| def get_rename_dict(dimensions): | ||
| def get_rename_dict(fields): | ||
| """Get a dictionary to rename the columns of a DataFrame.""" | ||
| return dict( | ||
| zip([dimension["id"] for dimension in dimensions], [dimension["alias"] for dimension in dimensions]) | ||
| ) | ||
| return {field["id"]: field["alias"] for field in fields} | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another |
||
|
|
||
|
|
||
| def get_one_period_change_series(series_current, series_previous, start_current, end_current, start_previous, end_previous): | ||
| def get_one_period_change_series( | ||
| series_current, | ||
| series_previous, | ||
| start_current, | ||
| end_current, | ||
| start_previous, | ||
| end_previous, | ||
| ): | ||
| """ | ||
| Get the percent change between two serieses, accounting for different numbers of days in the month. | ||
| :param series_current: the series representing the current month | ||
|
|
@@ -94,18 +117,40 @@ def get_one_period_change_series(series_current, series_previous, start_current, | |
| assert series_current.index.names == series_previous.index.names | ||
| # Reindex both serieses to have the same index | ||
| combined_index = series_current.index.union(series_previous.index) | ||
| current_length = float((dt.datetime.fromisoformat(end_current) - dt.datetime.fromisoformat(start_current)).days + 1) | ||
| previous_length = float((dt.datetime.fromisoformat(end_previous) - dt.datetime.fromisoformat(start_previous)).days + 1) | ||
| current_length = float( | ||
| ( | ||
| dt.datetime.fromisoformat(end_current) | ||
| - dt.datetime.fromisoformat(start_current) | ||
| ).days | ||
| + 1 | ||
| ) | ||
| previous_length = float( | ||
| ( | ||
| dt.datetime.fromisoformat(end_previous) | ||
| - dt.datetime.fromisoformat(start_previous) | ||
| ).days | ||
| + 1 | ||
| ) | ||
| assert current_length != 0 and previous_length != 0 | ||
| series_current_reindexed = series_current.reindex(combined_index).fillna(0) | ||
| # Adjust the values from the prior series to account for the different number of days in the month | ||
| series_previous_reindexed = (series_previous.reindex(combined_index) * current_length / previous_length) | ||
| change = ((series_current_reindexed / series_previous_reindexed) - 1).replace({np.inf: np.nan}) | ||
| series_previous_reindexed = ( | ||
| series_previous.reindex(combined_index) * current_length / previous_length | ||
| ) | ||
| change = ((series_current_reindexed / series_previous_reindexed) - 1).replace( | ||
| {np.inf: np.nan} | ||
| ) | ||
| return change | ||
|
|
||
|
|
||
| def get_change_over_time_df( | ||
| metrics, time_dimension, include_changes=True, additional_data_path=None, additional_data_behavior=None, strftime_format="%Y-%m", **other_params | ||
| metrics, | ||
| time_dimension, | ||
| include_changes=True, | ||
| additional_data_path=None, | ||
| additional_data_behavior=None, | ||
| strftime_format="%Y-%m", | ||
| **other_params, | ||
| ): | ||
| """ | ||
| Get a DataFrame with the change over time for the given metrics, renamed to match metric_titles | ||
|
|
@@ -124,8 +169,10 @@ def get_change_over_time_df( | |
| [metric["id"] for metric in metrics], | ||
| time_dimension["id"], | ||
| sort_results=[time_dimension["id"]], | ||
| df_processor=(lambda df: df.set_index(df.index + "01").sort_index(ascending=False)), | ||
| **other_params | ||
| df_processor=( | ||
| lambda df: df.set_index(df.index + "01").sort_index(ascending=False) | ||
| ), | ||
| **other_params, | ||
| ).rename({time_dimension["id"]: time_dimension["alias"]}) | ||
|
|
||
| df_combined = pd.DataFrame() | ||
|
|
@@ -137,23 +184,28 @@ def get_change_over_time_df( | |
| df_combined = df_api.add(df_saved.astype(int), fill_value=0)[::-1] | ||
| elif additional_data_behavior == ADDITIONAL_DATA_BEHAVIOR.REPLACE: | ||
| df_combined = pd.concat([df_saved, df_api], ignore_index=False) | ||
| df_combined = df_combined.loc[~df_combined.index.duplicated(keep="first")].sort_index(ascending=False) | ||
| df_combined = df_combined.loc[ | ||
| ~df_combined.index.duplicated(keep="first") | ||
| ].sort_index(ascending=False) | ||
| else: | ||
| df_combined = df_api | ||
|
|
||
| if include_changes: | ||
| df_combined[ | ||
| [metric["change_alias"] for metric in metrics] | ||
| ] = df_combined[ | ||
| [metric["alias"] for metric in metrics] | ||
| ].pct_change(periods=-1).replace({np.inf: np.nan}) | ||
| df_combined[[metric["change_alias"] for metric in metrics]] = ( | ||
| df_combined[[metric["alias"] for metric in metrics]] | ||
| .pct_change(periods=-1) | ||
| .replace({np.inf: np.nan}) | ||
| ) | ||
|
|
||
| if strftime_format is not None: | ||
| df_combined.index = pd.to_datetime(df_combined.index).strftime(strftime_format) | ||
|
|
||
| return df_combined.reset_index(names=time_dimension["alias"]) | ||
|
|
||
| def get_change_over_time_df_multiple_events(metric, events, time_dimension, **change_over_time_args): | ||
|
|
||
| def get_change_over_time_df_multiple_events( | ||
| metric, events, time_dimension, **change_over_time_args | ||
| ): | ||
| """ | ||
| Get a DataFrame with the change over time for the given metrics, renamed to match metric_titles | ||
| :param metrics: the metrics to be displayed | ||
|
|
@@ -170,11 +222,16 @@ def get_change_over_time_df_multiple_events(metric, events, time_dimension, **ch | |
| [metric], | ||
| time_dimension, | ||
| **change_over_time_args, | ||
| dimension_filter=f"eventName=={event['id']}" | ||
| ).rename( | ||
| columns={metric["alias"]: event["alias"], metric["change_alias"]: event["change_alias"]} | ||
| ).set_index(time_dimension["alias"]) | ||
| dimension_filter=f"eventName=={event['id']}", | ||
| ) | ||
| .rename( | ||
| columns={ | ||
| metric["alias"]: event["alias"], | ||
| metric["change_alias"]: event["change_alias"], | ||
| } | ||
| ) | ||
| .set_index(time_dimension["alias"]) | ||
| for event in events | ||
| ], | ||
| axis=1 | ||
| ) | ||
| axis=1, | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.