Feature/adaptive truncation - #70
Conversation
|
Warning Review limit reached
Next review available in: 58 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
mikkelfo
left a comment
There was a problem hiding this comment.
Generally looks fine, but again I'm worried about the amount of code added for these features. The more we add, the harder it is to maintain imo.
We obviously require some functionality so we cannot aimlessly minimise, but we're generally adding a high level of verbosity to these functions that we may never look at. I think it's a fine line we have to walk (@Sllambias thoughts?)
There was a problem hiding this comment.
I'm a little worried about the level of verbosity for the columns. Seems like a lot of code for an extensive level of verbosity (via different columns) that we may never look at
There was a problem hiding this comment.
True, agree - I've made much less checks and balances now. For writing up the code though, it is much easier to get a sense of what's up and down going overboard with how verbose it is. I've made it much more manageable now, totally agree with the sentiment.
| from pathlib import Path | ||
|
|
||
| CODE_COLUMN = DataSchema.code_name | ||
| MAPPED_CODE_COLUMN = "adaptive/mapped_code" |
There was a problem hiding this comment.
I think this should just be a default in the main function calling for this, or be contained in a yaml
There was a problem hiding this comment.
Yeah, moved it.
Sllambias
left a comment
There was a problem hiding this comment.
Agree with mikkel on the verbosity. I think the explanations make sense, but I think they're better placed in readme's that can be directly referred to from the yaml files and then I had a few comments. Happy to look again at a later time aswell
| from MEDS_transforms.utils import PKG_PFX, resolve_pkg_path | ||
| from pathlib import Path | ||
|
|
||
| CODE_COLUMN = DataSchema.code_name |
There was a problem hiding this comment.
Preferably just referred to as DataSchema.code_name in the code
There was a problem hiding this comment.
Agree, this again - opted for more DataSchema.code_name in the code instead.
| return resolve_pkg_path(filepath) if filepath.startswith(PKG_PFX) else Path(filepath) | ||
|
|
||
|
|
||
| def load_frame(filepath: str, label: str) -> pl.DataFrame: |
There was a problem hiding this comment.
Is this something that should be a more generally shared function or does it necessarily need to be nested here?
As opposed to being in a more general function file that is used by all of EHR2meds - it seems rather general maybe apart from path = resolve_resource_path(filepath)
There was a problem hiding this comment.
Yeah, super good point. This actually simplified things. I've added a io_utils.py now with a few helpers, and I think that setup makes more sense.
| external_mapping_mode: str, | ||
| ) -> pl.DataFrame: | ||
| """Select a local mapping and optionally overlay or replace it externally.""" | ||
| required_local = {CODE_COLUMN, MAPPED_CODE_COLUMN} |
There was a problem hiding this comment.
can you explain the different scenarios here? I'm trying to figure out if support all of these are necessary and can't really tell
Like what is the case where
- required_local is not subset of local_metadata and external_mapping_filepath is given
- required_local is not subset of local_metadata and external_mapping_filepath is not given
- required_local is subset of local_metadata and external_mapping_filepath is not given
- required_local is subset of local_metadata and external_mapping_filepath is given
There was a problem hiding this comment.
Yeah, super good question.
So, going through this, I rewrote much of the code because some of the instances don't really make sense.
Like for instance, you can imagine having to fit on your own data and not needing external data (say for instance, when you are training PHAIR light). The behavior before was also a bit messy in that it would add metadata for codes that were not in the external data, which is why it needed both. Now this can technically happen - you could imagine having a mismatch between data sources, where an upstream model doesn't include some data source that does have a hierarchy you want to truncate. However, I figured that this would be so rare that we are better off opting for a system where you can specify the stages for the pipeline that calculates the aggregate measures and thereby bin / truncate based on your own data, or otherwise use an external source as a backup, which is then taken as ground truth and not messed with. The other behavior was a mistake on my part; I thought it was smart, but I think it's stupid.
This also means that the behavior now is simpler. It doesn't cover as many different combinations, but it's easier to understand and more aligned with what the actual use cases are.
| if mode != "overlay": | ||
| raise ValueError("external_mapping_mode must be 'overlay' or 'replace'") | ||
| return pl.concat([external, local]).unique(CODE_COLUMN, keep="first", maintain_order=True) |
There was a problem hiding this comment.
Very minor suggestion but I think this is slightly nicer
| if mode != "overlay": | |
| raise ValueError("external_mapping_mode must be 'overlay' or 'replace'") | |
| return pl.concat([external, local]).unique(CODE_COLUMN, keep="first", maintain_order=True) | |
| if mode == "overlay": | |
| return pl.concat([external, local]).unique(CODE_COLUMN, keep="first", maintain_order=True) | |
| else: | |
| raise ValueError(f"external_mapping_mode must be 'overlay' or 'replace' but is {mode}") |
There was a problem hiding this comment.
I think this is a really nice file, and fits perfectly with what I also wanted to do with the other "tests" running in the workflows.
I'm a little concerned about (my perceived) complexity of these tests and I fear they may be prone to breaking quite easily and being relatively hard to maintain for maintainers (like me) not too familiar with the exact usage. But this is just a concern and may be completely wrong.
There was a problem hiding this comment.
yeah it was super useful for developing, but I think generally these files should be wiped after development or be integrated in a larger test suite. Don't think they make sense on their own, so I'll probably delete it now.
| """ | ||
| cfg = OmegaConf.to_container(stage_cfg, resolve=True) | ||
| namespaces = {str(namespace): str(profile) for namespace, profile in cfg["namespaces"].items()} | ||
| hierarchies = cfg.get("hierarchies", {}) |
There was a problem hiding this comment.
what happens when hierarchies is empty?
There was a problem hiding this comment.
made it so it errors instead of defaulting to an empty set
| } | ||
|
|
||
|
|
||
| def fit_mapping( |
There was a problem hiding this comment.
maybe this function should be split into two
There was a problem hiding this comment.
tried but didn't see a super good way of doing it. There are parts of the loop that needs previous parts of the code, so a natural split is kind of hard to make I think. Happy to take a second look if you see a path forward!
Trying to implement adaptive truncation in the pipeline