diff --git a/docs/how-to/errors.md b/docs/how-to/errors.md
index d718608fb..c6c131f7d 100644
--- a/docs/how-to/errors.md
+++ b/docs/how-to/errors.md
@@ -816,7 +816,7 @@ age = patients.age_on("2023-01-01")
dataset.age_group = case(
when(age < 10).then(1),
when(age > 80).then(2),
- default="unknown",
+ otherwise="unknown",
)
```
@@ -842,7 +842,7 @@ age = patients.age_on("2023-01-01")
dataset.age_group = case(
when(age < 10).then(1),
when(age > 80).then(2),
- default=0,
+ otherwise=0,
)
```
diff --git a/docs/how-to/examples.md b/docs/how-to/examples.md
index a665535ba..25c3da2ae 100644
--- a/docs/how-to/examples.md
+++ b/docs/how-to/examples.md
@@ -107,7 +107,7 @@ dataset.age_band = case(
when(age < 60).then("40-59"),
when(age < 80).then("60-79"),
when(age >= 80).then("80+"),
- default="missing",
+ otherwise="missing",
)
dataset.define_population(patients.exists_for_patient())
```
@@ -227,7 +227,7 @@ dataset.imd_quintile = case(
when(imd < int(32844 * 3 / 5)).then("3"),
when(imd < int(32844 * 4 / 5)).then("4"),
when(imd < int(32844 * 5 / 5)).then("5 (least deprived)"),
- default="unknown"
+ otherwise="unknown"
)
dataset.define_population(patients.exists_for_patient())
```
diff --git a/docs/includes/generated_docs/language__functions.md b/docs/includes/generated_docs/language__functions.md
index da02f7099..9b6f599a8 100644
--- a/docs/includes/generated_docs/language__functions.md
+++ b/docs/includes/generated_docs/language__functions.md
@@ -1,6 +1,6 @@
- case(*when_thens, default=None)
+ case(*when_thens, otherwise=None, default=None)
Take a sequence of condition-values of the form:
@@ -9,8 +9,8 @@ when(condition).then(value)
```
And evaluate them in order, returning the value of the first condition which
-evaluates True. If no condition matches and a `default` is specified then return
-that, otherwise return NULL.
+evaluates True. If no condition matches, return the `otherwise` value; if no
+`otherwise` value is specified then return NULL.
For example:
```py
@@ -18,7 +18,7 @@ category = case(
when(size < 10).then("small"),
when(size < 20).then("medium"),
when(size >= 20).then("large"),
- default="unknown",
+ otherwise="unknown",
)
```
@@ -31,7 +31,7 @@ A simpler form is available when there is a single condition. This example:
```py
category = case(
when(size < 15).then("small"),
- default="large",
+ otherwise="large",
)
```
@@ -39,6 +39,9 @@ can be rewritten as:
```py
category = when(size < 15).then("small").otherwise("large")
```
+
+Note that the `default` argument is an older alias for `otherwise`: it will be
+removed in future versions of ehrQL and should not be used.
diff --git a/docs/includes/generated_docs/language__series.md b/docs/includes/generated_docs/language__series.md
index 15b339709..c9c9746b7 100644
--- a/docs/includes/generated_docs/language__series.md
+++ b/docs/includes/generated_docs/language__series.md
@@ -75,14 +75,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -199,14 +209,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -340,14 +360,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -481,14 +511,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -708,14 +748,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -913,14 +963,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -1163,14 +1223,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -1368,14 +1438,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -1594,14 +1674,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -1844,14 +1934,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -2081,14 +2181,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
@@ -2181,14 +2291,24 @@ NULL, and False otherwise.
Return the inverse of `is_null()` above.
+
+ when_null_then(other)
+
+
+
+Replace any NULL value in this series with the corresponding value in `other`.
+
+Note that `other` must be of the same type as this series.
+
+
if_null_then(other)
-Replace any NULL value in this series with the corresponding value in `other`.
+Deprecated alias for `when_null_then()`
-Note that `other` must be of the same type as this series.
+This will be removed in future versions of ehrQL and shoud not be used.
diff --git a/docs/includes/generated_docs/schemas/beta.tpp.md b/docs/includes/generated_docs/schemas/beta.tpp.md
index b3b2b5057..183de3e9a 100644
--- a/docs/includes/generated_docs/schemas/beta.tpp.md
+++ b/docs/includes/generated_docs/schemas/beta.tpp.md
@@ -57,7 +57,7 @@ from which other larger geographic representations can be derived
when(imd < int(32844 * 3 / 5)).then("3"),
when(imd < int(32844 * 4 / 5)).then("4"),
when(imd < int(32844 * 5 / 5)).then("5 (least deprived)"),
- default="unknown"
+ otherwise="unknown"
)
```
@@ -241,7 +241,7 @@ spanning_addrs = addresses.where(addresses.start_date <= date).except_where(
addresses.end_date < date
)
ordered_addrs = spanning_addrs.sort_by(
- case(when(addresses.has_postcode).then(1), default=0),
+ case(when(addresses.has_postcode).then(1), otherwise=0),
addresses.start_date,
addresses.end_date,
addresses.address_id,
diff --git a/docs/includes/generated_docs/specs.md b/docs/includes/generated_docs/specs.md
index f20352901..074a1dbfb 100644
--- a/docs/includes/generated_docs/specs.md
+++ b/docs/includes/generated_docs/specs.md
@@ -1262,7 +1262,7 @@ returns the following patient series:
### 6.4 Replace missing values
-#### 6.4.1 If null then integer column
+#### 6.4.1 When null then integer column
This example makes use of a patient-level table named `p` containing the following data:
@@ -1274,7 +1274,7 @@ This example makes use of a patient-level table named `p` containing the followi
| 4| |
```python
-p.i1.if_null_then(0)
+p.i1.when_null_then(0)
```
returns the following patient series:
@@ -1287,7 +1287,7 @@ returns the following patient series:
-#### 6.4.2 If null then boolean column
+#### 6.4.2 When null then boolean column
This example makes use of a patient-level table named `p` containing the following data:
@@ -1299,7 +1299,7 @@ This example makes use of a patient-level table named `p` containing the followi
| 4| |
```python
-p.i1.is_in([101, 201]).if_null_then(False)
+p.i1.is_in([101, 201]).when_null_then(False)
```
returns the following patient series:
@@ -2760,7 +2760,7 @@ This example makes use of a patient-level table named `p` containing the followi
case(
when(p.i1 < 8).then(p.i1),
when(p.i1 > 8).then(100),
- default=0,
+ otherwise=0,
)
```
returns the following patient series:
diff --git a/ehrql/query_language.py b/ehrql/query_language.py
index 9c3ecdbd9..ba187ed12 100644
--- a/ehrql/query_language.py
+++ b/ehrql/query_language.py
@@ -203,7 +203,7 @@ def is_not_null(self):
"""
return self.is_null().__invert__()
- def if_null_then(self, other):
+ def when_null_then(self, other):
"""
Replace any NULL value in this series with the corresponding value in `other`.
@@ -211,9 +211,17 @@ def if_null_then(self, other):
"""
return case(
when(self.is_not_null()).then(self),
- default=self._cast(other),
+ otherwise=self._cast(other),
)
+ def if_null_then(self, other):
+ """
+ Deprecated alias for `when_null_then()`
+
+ This will be removed in future versions of ehrQL and shoud not be used.
+ """
+ return self.when_null_then(other)
+
def is_in(self, other):
"""
Return a boolean series which is True for each value in this series which is
@@ -251,7 +259,7 @@ def map_values(self, mapping, default=None):
when(self == from_value).then(to_value)
for from_value, to_value in mapping.items()
],
- default=default,
+ otherwise=default,
)
@@ -1347,11 +1355,11 @@ def __init__(self, condition, value):
self._condition = condition
self._value = value
- def otherwise(self, default):
- return case(self, default=default)
+ def otherwise(self, value):
+ return case(self, otherwise=value)
-def case(*when_thens, default=None):
+def case(*when_thens, otherwise=None, default=None):
"""
Take a sequence of condition-values of the form:
```py
@@ -1359,8 +1367,8 @@ def case(*when_thens, default=None):
```
And evaluate them in order, returning the value of the first condition which
- evaluates True. If no condition matches and a `default` is specified then return
- that, otherwise return NULL.
+ evaluates True. If no condition matches, return the `otherwise` value; if no
+ `otherwise` value is specified then return NULL.
For example:
```py
@@ -1368,7 +1376,7 @@ def case(*when_thens, default=None):
when(size < 10).then("small"),
when(size < 20).then("medium"),
when(size >= 20).then("large"),
- default="unknown",
+ otherwise="unknown",
)
```
@@ -1381,7 +1389,7 @@ def case(*when_thens, default=None):
```py
category = case(
when(size < 15).then("small"),
- default="large",
+ otherwise="large",
)
```
@@ -1390,14 +1398,20 @@ def case(*when_thens, default=None):
category = when(size < 15).then("small").otherwise("large")
```
+ Note that the `default` argument is an older alias for `otherwise`: it will be
+ removed in future versions of ehrQL and should not be used.
"""
+ if default is not None:
+ if otherwise is not None:
+ raise ValueError("Use `otherwise` instead of `default`")
+ otherwise = default
cases = _DictArg((case._condition, case._value) for case in when_thens)
- # If we don't want a default then we shouldn't supply an argument, or else it will
- # get converted into `Value(None)` which is not what we want
- if default is None:
+ # If we don't want an `otherwise` value then we shouldn't supply an argument, or
+ # else it will get converted into `Value(None)` which is not what we want
+ if otherwise is None:
return _apply(qm.Case, cases)
else:
- return _apply(qm.Case, cases, default)
+ return _apply(qm.Case, cases, otherwise)
# HORIZONTAL AGGREGATION FUNCTIONS
diff --git a/ehrql/tables/beta/tpp.py b/ehrql/tables/beta/tpp.py
index 60d090670..39bb23667 100644
--- a/ehrql/tables/beta/tpp.py
+++ b/ehrql/tables/beta/tpp.py
@@ -61,7 +61,7 @@ class addresses(EventFrame):
when(imd < int(32844 * 3 / 5)).then("3"),
when(imd < int(32844 * 4 / 5)).then("4"),
when(imd < int(32844 * 5 / 5)).then("5 (least deprived)"),
- default="unknown"
+ otherwise="unknown"
)
```
@@ -152,7 +152,7 @@ def for_patient_on(self, date):
self.end_date < date
)
ordered_addrs = spanning_addrs.sort_by(
- case(when(self.has_postcode).then(1), default=0),
+ case(when(self.has_postcode).then(1), otherwise=0),
self.start_date,
self.end_date,
self.address_id,
diff --git a/tests/spec/case_expressions/test_case.py b/tests/spec/case_expressions/test_case.py
index ae063ee79..5d5cfdfd0 100644
--- a/tests/spec/case_expressions/test_case.py
+++ b/tests/spec/case_expressions/test_case.py
@@ -41,7 +41,7 @@ def test_case_with_default(spec_test):
case(
when(p.i1 < 8).then(p.i1),
when(p.i1 > 8).then(100),
- default=0,
+ otherwise=0,
),
{
1: 6,
diff --git a/tests/spec/series_ops/test_if_null_then.py b/tests/spec/series_ops/test_when_null_then.py
similarity index 72%
rename from tests/spec/series_ops/test_if_null_then.py
rename to tests/spec/series_ops/test_when_null_then.py
index f37957ba4..bc322ce0b 100644
--- a/tests/spec/series_ops/test_if_null_then.py
+++ b/tests/spec/series_ops/test_when_null_then.py
@@ -15,10 +15,10 @@
}
-def test_if_null_then_integer_column(spec_test):
+def test_when_null_then_integer_column(spec_test):
spec_test(
table_data,
- p.i1.if_null_then(0),
+ p.i1.when_null_then(0),
{
1: 101,
2: 201,
@@ -28,10 +28,10 @@ def test_if_null_then_integer_column(spec_test):
)
-def test_if_null_then_boolean_column(spec_test):
+def test_when_null_then_boolean_column(spec_test):
spec_test(
table_data,
- p.i1.is_in([101, 201]).if_null_then(False),
+ p.i1.is_in([101, 201]).when_null_then(False),
{
1: True,
2: True,
diff --git a/tests/spec/toc.py b/tests/spec/toc.py
index 2683f6088..a8dda8c89 100644
--- a/tests/spec/toc.py
+++ b/tests/spec/toc.py
@@ -32,7 +32,7 @@
"test_equality",
"test_containment",
"test_map_values",
- "test_if_null_then",
+ "test_when_null_then",
"test_maximum_of_and_minimum_of_patient_series",
"test_maximum_of_and_minimum_of_event_series",
],
diff --git a/tests/unit/test_query_language.py b/tests/unit/test_query_language.py
index 8f6a0dd0e..611bd9236 100644
--- a/tests/unit/test_query_language.py
+++ b/tests/unit/test_query_language.py
@@ -27,6 +27,7 @@
Series,
StrEventSeries,
StrPatientSeries,
+ case,
compile,
create_dataset,
days,
@@ -36,6 +37,7 @@
table_from_file,
table_from_rows,
weeks,
+ when,
years,
)
from ehrql.query_model.column_specs import ColumnSpec
@@ -759,19 +761,30 @@ def test_duration_generate_intervals_rejects_invalid_arguments(
],
)
def test_count_episodes_for_patient_rejects_invalid_arguments(maximum_gap, error):
- @table
- class e(EventFrame):
- d = Series(date)
-
with pytest.raises((TypeError, ValueError), match=error):
- e.d.count_episodes_for_patient(maximum_gap)
+ events.event_date.count_episodes_for_patient(maximum_gap)
def test_count_episodes_for_patient_handles_weeks():
- @table
- class e(EventFrame):
- d = Series(date)
-
- using_days = e.d.count_episodes_for_patient(days(14))
- using_weeks = e.d.count_episodes_for_patient(weeks(2))
+ using_days = events.event_date.count_episodes_for_patient(days(14))
+ using_weeks = events.event_date.count_episodes_for_patient(weeks(2))
assert using_days._qm_node == using_weeks._qm_node
+
+
+def test_case_accepts_default_as_alias_for_otherwise():
+ case_otherwise = case(when(events.f > 10).then("foo"), otherwise="bar")
+ case_default = case(when(events.f > 10).then("foo"), default="bar")
+
+ assert case_otherwise._qm_node == case_default._qm_node
+
+
+def test_case_rejects_default_and_otherwise_supplied_together():
+ with pytest.raises(ValueError, match="Use `otherwise` instead of `default`"):
+ case(when(events.f > 10).then("foo"), default="bar", otherwise="baz")
+
+
+def test_if_null_then_alias():
+ if_null = events.f.if_null_then(0.0)
+ when_null = events.f.when_null_then(0.0)
+
+ assert if_null._qm_node == when_null._qm_node