Skip to content
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

Local frequency analysis #20

Closed
wants to merge 29 commits into from

Conversation

sebastienlanglois
Copy link
Contributor

Pull Request Checklist:

  • This PR addresses an already opened issue (for bug fixes / features)
    • This PR fixes #xyz
  • (If applicable) Documentation has been added / updated (for bug fixes / features).
  • (If applicable) Tests have been added.
  • HISTORY.rst has been updated (with summary of main changes).
    • Link to issue (:issue:number) and pull request (:pull:number) has been added.

What kind of change does this PR introduce?

  • Local frequency analysis module
  • Notebook example

Does this PR introduce a breaking change?

No

Other information:

A few things are missing in order to complete this pull request :

  • local.py should be simplified and generalised. For instance, we should only assume 2 dimensions as inputs (time, id). The rest should be carried forward if desired but shouldn't be required.
  • Once local.py is completed, complete the notebook example
  • Add notebook to docs
  • Add tests

@sebastienlanglois sebastienlanglois added the enhancement New feature or request label Aug 28, 2023
@github-actions
Copy link

Welcome, new contributor!

It appears that this is your first Pull Request. To give credit where it's due, we ask that you add your information to the AUTHORS.rst and .zenodo.json.:

  • The relevant author information has been added to AUTHORS.rst and .zenodo.json.

Please make sure you've read our contributing guide. We look forward to reviewing your Pull Request shortly ✨

@sebastienlanglois sebastienlanglois marked this pull request as draft August 28, 2023 05:16
@RondeauG
Copy link
Collaborator

RondeauG commented Aug 30, 2023

Je vois plusieurs améliorations potentielles qui pourraient être faites à local.py, par exemple en utilisant des fonctions qui existent déjà tel quel dans xclim, plutôt que de les réécrire à même la classe Data.

Pour que mes changements soient le plus explicite possible et qu'on puisse facilement les commenter, je partirais une nouvelle branche à partir de cette PR. Qu'en pensez-vous ?

@sebastienlanglois
Copy link
Contributor Author

sebastienlanglois commented Aug 31, 2023

@RondeauG Oui je suis d'accord avec ton plan de match. On est bien conscient qu'il y a des optimisations à faire autant au niveau du code python qu'au niveau de la réutilisation des librairies existantes. N'hésites pas à contacter @TC-FF pour toute question sur les fonctionnalités du code.

J'ai commencé à monter un notebook (non complété) dans ce pull request pour voir plus en détails le genre d'opérations qu'on voudrait faire et éventuellement, pour ajouter à la doc

@RondeauG
Copy link
Collaborator

Alright, excellent. Je vais tenter de faire le maximums de changements d'ici la réunion de septembre.

Copy link
Collaborator

@RondeauG RondeauG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still working on my PR, but I think that I spotted an error in the code.

Comment on lines +508 to +522
.sum()
.where(ds.get_bool_over_tolerence(tolerence, "Volumes"), drop=True)
)
self.rm_season("Volumes")
# Transform tp hm³
# TODO add start and end and clear other attributes
grouped_ds = (
grouped_ds
* xr.apply_ufunc(
conversion_factor_to_hm3,
grouped_ds["timestep"],
input_core_dims=[[]],
vectorize=True,
)
* (dates[1] - dates[0])
Copy link
Collaborator

@RondeauG RondeauG Sep 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is written right now, I think that you basically sum twice. The first time is at Line 508 (and would result in biased results if there are NaNs, so .mean() is more robust), and the second time is at Line 522 when you multiply the results by the number of days.

Suggested change
.sum()
.where(ds.get_bool_over_tolerence(tolerence, "Volumes"), drop=True)
)
self.rm_season("Volumes")
# Transform tp hm³
# TODO add start and end and clear other attributes
grouped_ds = (
grouped_ds
* xr.apply_ufunc(
conversion_factor_to_hm3,
grouped_ds["timestep"],
input_core_dims=[[]],
vectorize=True,
)
* (dates[1] - dates[0])
.mean()
.where(ds.get_bool_over_tolerence(tolerence, "Volumes"), drop=True)
)
self.rm_season("Volumes")
# Transform tp hm³
# TODO add start and end and clear other attributes
grouped_ds = (
grouped_ds
* xr.apply_ufunc(
conversion_factor_to_hm3,
grouped_ds["timestep"],
input_core_dims=[[]],
vectorize=True,
)
* (dates[1] - dates[0] + 1)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say I have: ds2.streamflow.isel(time=slice(0,5)).values
array([[4.56, 4.25, 3.99, 3.77, 3.62]], dtype=float32)

Daily volumes: ds2.streamflow.isel(time=slice(0,5)).values * 60*60*24
array([[393984., 367200., 344736., 325728., 312768.]], dtype=float32)

Sum: ds2.streamflow.isel(time=slice(0,5)).values * 60*60*24).sum()
1744416.0 m³, or 1.744416 hm³. This should be the right answer, no?

However, ds_fa_vol = ds_fa.calculate_volume(tolerence=0.15, dates=[1, 5])
6.97766418, which is 1.744416 * (5 - 1)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TC-FF Qu'en penses-tu ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RondeauG Il faut vraiment mettre en place des tests unitaires ! Mais en effet, c'est un bug. Les résultats des pointes ont étés (un peu) validés, mais pas du tout ceux des volumes.
Pour la correction, je pense que ce serait mieux de laisser sum() plutôt que de faire mean() * (dates[1] - dates[0] + 1).

Copy link
Collaborator

@RondeauG RondeauG Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-ce qu'il n'y a pas un danger avec la combinaison de .sum() et tolerance ? Si tu as 15% de données manquantes pour une année donnée, la somme risque d'être pas mal biaisée.

EDIT: Cela dit, facile d'ajouter les deux options.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour la tolérance, ça me semble ok, comme c'est par année, si on dépasse, l'année n'est pas inclue. Mais en effet si l'on est sous la tolérance, est-ce qu'on veut pondérer selon le nb de valeurs manquante ? Dans un cas de datas fixes, je dirais oui, dans un cas de dates variable, ¸a me semble moins nécessaire.

@RondeauG
Copy link
Collaborator

RondeauG commented Sep 1, 2023

Je viens de terminer de regarder local.py plus en détails. Voici mon review plus "high level":

  1. J'ai pu reproduire exactement les mêmes résultats en utilisant Data/Local et une version brouillon basées sur les fonctionnalités xclim que je compte vous proposer, ce qui est une excellente nouvelle. La seule exception est pour les volumes (voir mon commentaire plus tôt aujourd'hui).
  2. On va pouvoir se débarrasser d'essentiellement toute la classe Data (et de l'équivalent qui se trouve dans Local), sauf peut-être pour quelques goodies. Les fonctionnalités qui s'y trouvent peuvent être remplacées par des indicateurs custom dans xclim.
# Get the maximum streamflow between DOY 1 and 120, tolerance of 15% missing values
qmax_winter = xc.core.indicator.Indicator.from_dict(
                                 data={"base": "stats",
                                            "input": {"da": "streamflow"},
                                            "parameters":
                                                            {"op": "max",
                                                             "indexer": {"doy_bounds": [1, 120]}},
                                            "missing": "pct",
                                            "missing_options": {"tolerance": 0.15}
                                            },
                                  identifier="qmax_winter",
                                  module="fa",
                              )

L'équivalent est possible aussi pour les volumes.

  1. Pour Local, l'idée est la bonne. Tel que spécifié dans la documentation, il faut y aller un peu plus à la main si on veut autant les paramètres que les périodes de retour, et pour traiter adéquatement les données manquantes. Je crois que ça vaudrait éventuellement une PR dans xclim, mais entre temps on peut rester avec ce que vous avez. Je vais quand même probablement proposer des amélioration à la structure de la classe elle-même.

@sebastienlanglois
Copy link
Contributor Author

Je viens de terminer de regarder local.py plus en détails. Voici mon review plus "high level":

  1. J'ai pu reproduire exactement les mêmes résultats en utilisant Data/Local et une version brouillon basées sur les fonctionnalités xclim que je compte vous proposer, ce qui est une excellente nouvelle. La seule exception est pour les volumes (voir mon commentaire plus tôt aujourd'hui).
  2. On va pouvoir se débarrasser d'essentiellement toute la classe Data (et de l'équivalent qui se trouve dans Local), sauf peut-être pour quelques goodies. Les fonctionnalités qui s'y trouvent peuvent être remplacées par des indicateurs custom dans xclim.
# Get the maximum streamflow between DOY 1 and 120, tolerance of 15% missing values
qmax_winter = xc.core.indicator.Indicator.from_dict(
                                 data={"base": "stats",
                                            "input": {"da": "streamflow"},
                                            "parameters":
                                                            {"op": "max",
                                                             "indexer": {"doy_bounds": [1, 120]}},
                                            "missing": "pct",
                                            "missing_options": {"tolerance": 0.15}
                                            },
                                  identifier="qmax_winter",
                                  module="fa",
                              )

L'équivalent est possible aussi pour les volumes.

  1. Pour Local, l'idée est la bonne. Tel que spécifié dans la documentation, il faut y aller un peu plus à la main si on veut autant les paramètres que les périodes de retour, et pour traiter adéquatement les données manquantes. Je crois que ça vaudrait éventuellement une PR dans xclim, mais entre temps on peut rester avec ce que vous avez. Je vais quand même probablement proposer des amélioration à la structure de la classe elle-même.

Oui, ça me semble une bonne idée de ne plus avoir Data. J'avais un inconfort avec puisqu'il aurait fallu encore la redéfinir pour les analyses fréquentielles régionales et que ça pouvait devenir mélangeant.

Pour ce qui est des fonctionnalités de Local, on voulait couvrir : les périodes de retour, les critères d'ajustement (AIC, BIC, AICC, etc.), les quantiles, les méthodes d'ajustement des paramètres (max vraisemblance, L-moments, etc.), etc. @TC-FF avait aussi commencé une fonction pour visualiser le fit avec hvplot qui pourrait être ajoutée. Idéalement, on pourrait aussi ajouter une dimension aléatoire. Par exemple, on peut vouloir faire une analyse fréquentielle sur le vent, auquel cas on aurait une dimension de direction (NNE, SSE, etc.). Idéalement, dans le résultat final, on aurait nos résultats pour chaque direction dans ce cas. Aussi, il y avait l'argument calculated dont l'objectif était de passer directement les maximas à Local, car parfois ils sont précalculés.

@Zeitsperre Zeitsperre force-pushed the Class-Data-in-frequency_analysis.local branch from 67e875e to 7b4fb8a Compare October 3, 2023 15:03
@Zeitsperre Zeitsperre force-pushed the Class-Data-in-frequency_analysis.local branch from 11ee11a to b479682 Compare October 3, 2023 15:05
@RondeauG RondeauG mentioned this pull request Oct 4, 2023
5 tasks
@RondeauG RondeauG added the wontfix This will not be worked on label Oct 12, 2023
@RondeauG
Copy link
Collaborator

This PR was replaced by #27.

@RondeauG RondeauG closed this Oct 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request wontfix This will not be worked on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants