Skip to content

Commit 4316416

Browse files
authored
Release 0.1.17
Merge pull request #50 from PEtab-dev/release_0.1.17
2 parents a9e126a + fa1de31 commit 4316416

File tree

6 files changed

+119
-38
lines changed

6 files changed

+119
-38
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## 0.1 series
44

5+
### 0.1.17
6+
7+
* Updated package URL
8+
* Fixed noise formula check (#49)
9+
* Fixed override check and add noise formula check (#48)
10+
* Fixed timepoint override check (#47)
11+
512
### 0.1.16
613

714
Update python version for pypi deployment, no further changes

petab/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ def flatten_timepoint_specific_output_overrides(
139139
replacement_id = ''
140140
for field in possible_groupvars:
141141
if field in groupvars:
142-
val = groupvar[groupvars.index(field)
143-
].replace(';', '_').replace('.', '_')
142+
val = str(groupvar[groupvars.index(field)
143+
]).replace(';', '_').replace('.', '_')
144144
if replacement_id == '':
145145
replacement_id = val
146146
elif val != '':

petab/lint.py

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import logging
55
import numbers
66
import re
7-
from typing import Optional, Iterable, Union
7+
from typing import Optional, Iterable, Any
88
from collections import Counter
99

1010
import libsbml
@@ -526,68 +526,106 @@ def assert_parameter_estimate_is_boolean(parameter_df: pd.DataFrame) -> None:
526526
f"Expected 0 or 1 but got {estimate} in {ESTIMATE} column.")
527527

528528

529+
def is_scalar_float(x: Any):
530+
"""
531+
Checks whether input is a number or can be transformed into a number
532+
via float
533+
:param x:
534+
input
535+
:return:
536+
True if is or can be converted to number, False otherwise.
537+
"""
538+
if isinstance(x, numbers.Number):
539+
return True
540+
try:
541+
float(x)
542+
return True
543+
except (ValueError, TypeError):
544+
return False
545+
546+
529547
def measurement_table_has_timepoint_specific_mappings(
530-
measurement_df: pd.DataFrame) -> bool:
548+
measurement_df: pd.DataFrame,
549+
allow_scalar_numeric_noise_parameters: bool = False,
550+
allow_scalar_numeric_observable_parameters: bool = False,
551+
) -> bool:
531552
"""
532553
Are there time-point or replicate specific parameter assignments in the
533554
measurement table.
534555
535556
Arguments:
536-
measurement_df: PEtab measurement table
557+
measurement_df:
558+
PEtab measurement table
559+
560+
allow_scalar_numeric_noise_parameters:
561+
ignore scalar numeric assignments to noiseParamater placeholders
562+
563+
allow_scalar_numeric_observable_parameters:
564+
ignore scalar numeric assignments to observableParamater
565+
placeholders
537566
538567
Returns:
539-
True if there are time-point or replicate specific parameter
540-
assignments in the measurement table, False otherwise.
568+
True if there are time-point or replicate specific (non-numeric)
569+
parameter assignments in the measurement table, False otherwise.
541570
"""
542571
# since we edit it, copy it first
543572
measurement_df = copy.deepcopy(measurement_df)
544573

545-
def is_numeric(x: Union[str, numbers.Number]) -> bool:
546-
"""
547-
Checks whether x can be transformed into a (list of) float(s)
548-
:param x:
549-
number or string containing numbers seperated by ;
550-
:return:
551-
True if conversion is possible for all values
552-
"""
553-
if isinstance(x, numbers.Number):
554-
return True
555-
if not isinstance(x, str):
556-
return False
557-
try:
558-
[float(y) for y in x.split(';')]
559-
return True
560-
except (ValueError, TypeError):
561-
return False
562-
563574
# mask numeric values
564-
for col in [OBSERVABLE_PARAMETERS, NOISE_PARAMETERS]:
575+
for col, allow_scalar_numeric in [
576+
(OBSERVABLE_PARAMETERS, allow_scalar_numeric_observable_parameters),
577+
(NOISE_PARAMETERS, allow_scalar_numeric_noise_parameters)
578+
]:
565579
if col not in measurement_df:
566580
continue
567-
measurement_df.loc[measurement_df[col].apply(is_numeric), col] = np.nan
581+
582+
measurement_df[col] = measurement_df[col].apply(str)
583+
584+
if allow_scalar_numeric:
585+
measurement_df.loc[
586+
measurement_df[col].apply(is_scalar_float), col
587+
] = np.nan
568588

569589
grouping_cols = core.get_notnull_columns(
570590
measurement_df,
571591
[OBSERVABLE_ID,
572592
SIMULATION_CONDITION_ID,
573593
PREEQUILIBRATION_CONDITION_ID,
574594
OBSERVABLE_PARAMETERS,
575-
NOISE_PARAMETERS,
576-
])
577-
grouped_df = measurement_df.groupby(grouping_cols,
578-
dropna=False).size().reset_index()
595+
NOISE_PARAMETERS])
596+
grouped_df = measurement_df.groupby(grouping_cols, dropna=False)
579597

580598
grouping_cols = core.get_notnull_columns(
581-
grouped_df,
599+
measurement_df,
582600
[OBSERVABLE_ID,
583601
SIMULATION_CONDITION_ID,
584602
PREEQUILIBRATION_CONDITION_ID])
585-
grouped_df2 = grouped_df.groupby(grouping_cols).size().reset_index()
603+
grouped_df2 = measurement_df.groupby(grouping_cols)
586604

587605
# data frame has timepoint specific overrides if grouping by noise
588606
# parameters and observable parameters in addition to observable,
589607
# condition and preeq id yields more groups
590-
return len(grouped_df.index) != len(grouped_df2.index)
608+
return len(grouped_df) != len(grouped_df2)
609+
610+
611+
def observable_table_has_nontrivial_noise_formula(
612+
observable_df: pd.DataFrame) -> bool:
613+
"""
614+
Does any observable have a noise formula that is not just a single
615+
parameter?
616+
617+
Arguments:
618+
observable_df: PEtab observable table
619+
620+
Returns:
621+
True if any noise formula does not consist of a single identifier,
622+
False otherwise.
623+
"""
624+
625+
return not observable_df[NOISE_FORMULA].apply(
626+
lambda x: is_scalar_float(x) or
627+
re.match(r'^[\w]+$', str(x)) is not None
628+
).all()
591629

592630

593631
def measurement_table_has_observable_parameter_numeric_overrides(
@@ -598,7 +636,7 @@ def measurement_table_has_observable_parameter_numeric_overrides(
598636
measurement_df: PEtab measurement table
599637
600638
Returns:
601-
True if there any numbers to override observable parameters,
639+
True if there are any numbers to override observable/noise parameters,
602640
False otherwise.
603641
"""
604642
if OBSERVABLE_PARAMETERS not in measurement_df:

petab/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""PEtab library version"""
2-
__version__ = '0.1.16'
2+
__version__ = '0.1.17'

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def absolute_links(txt):
5353
long_description_content_type="text/markdown",
5454
author='The PEtab developers',
5555
author_email='daniel.weindl@helmholtz-muenchen.de',
56-
url='https://github.com/PEtab-dev/PEtab',
56+
url='https://github.com/PEtab-dev/libpetab-python',
5757
packages=find_packages(exclude=['doc*', 'test*']),
5858
install_requires=['numpy>=1.15.1',
5959
'pandas>=1.2.0',

tests/test_lint.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,53 @@ def test_measurement_table_has_timepoint_specific_mappings():
5757
PREEQUILIBRATION_CONDITION_ID: [nan, nan],
5858
TIME: [1.0, 2.0],
5959
OBSERVABLE_PARAMETERS: ['obsParOverride', ''],
60-
NOISE_PARAMETERS: ['', '']
60+
NOISE_PARAMETERS: ['1.0', 1.0]
6161
})
6262

6363
assert lint.measurement_table_has_timepoint_specific_mappings(
6464
measurement_df) is True
6565

66+
# both measurements different anyways
6667
measurement_df.loc[1, OBSERVABLE_ID] = 'obs2'
68+
assert lint.measurement_table_has_timepoint_specific_mappings(
69+
measurement_df) is False
6770

71+
# mixed numeric string
72+
measurement_df.loc[1, OBSERVABLE_ID] = 'obs1'
73+
measurement_df.loc[1, OBSERVABLE_PARAMETERS] = 'obsParOverride'
6874
assert lint.measurement_table_has_timepoint_specific_mappings(
6975
measurement_df) is False
7076

77+
# different numeric values
78+
measurement_df.loc[1, NOISE_PARAMETERS] = 2.0
79+
assert lint.measurement_table_has_timepoint_specific_mappings(
80+
measurement_df) is True
81+
assert lint.measurement_table_has_timepoint_specific_mappings(
82+
measurement_df, allow_scalar_numeric_noise_parameters=True) is False
83+
84+
85+
def test_observable_table_has_nontrivial_noise_formula():
86+
# Ensure we fail if we have nontrivial noise formulas
87+
88+
observable_df = pd.DataFrame(data={
89+
OBSERVABLE_ID: ['0obsPar1noisePar', '2obsPar0noisePar',
90+
'3obsPar0noisePar'],
91+
OBSERVABLE_FORMULA: ['1.0',
92+
'1.0',
93+
'1.0'],
94+
NOISE_FORMULA: ['noiseParameter1_0obsPar1noisePar + 3.0',
95+
1e18,
96+
'1e18']
97+
})
98+
99+
assert lint.observable_table_has_nontrivial_noise_formula(observable_df)\
100+
is True
101+
102+
observable_df.loc[0, NOISE_FORMULA] = 'sigma1'
103+
104+
assert lint.observable_table_has_nontrivial_noise_formula(observable_df) \
105+
is False
106+
71107

72108
def test_assert_overrides_match_parameter_count():
73109
# Ensure we recognize and fail if we have wrong number of overrides

0 commit comments

Comments
 (0)