It seems to me that there is an error in the description of the groupby function regarding how it works when an item does not have a grouping attribute. The description of the default argument says:
The default value to use for items that do not have the specified attribute. If not provided, items without the attribute are excluded.
However, this code
{% for device_class, entities in states
| groupby("attributes.device_class") %}
{{ device_class }}: {{ entities | length }} sensors
{% endfor %}
results in: UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'device_class'.
With default argument provided, it works:
{% for device_class, entities in states
| groupby("attributes.device_class","missing") %}
{{ device_class }}: {{ entities | length }} sensors
{% endfor %}
battery: 12 sensors
button: 5 sensors
(...)
missing: 683 sensors
I thought that maybe it’s only like that with “complex” dot access, but it’s the same with simple attribute name without dot, e.g.
{% for device_class, entities in states
| groupby("not_existing_attribute") %}
{{ device_class }}: {{ entities | length }} sensors
{% endfor %}
I also have doubts about this statement in the " Good to know" section:
The input must be sorted by the grouping attribute already. This filter only groups adjacent items with matching values.
I ran a test with a list of dictionaries that aren’t sorted by the grouping attribute:
{%
set src = [
dict(name='item 1', attributes=dict(device_class='c1',state=1)),
dict(name='item 2', attributes=dict(device_class='c1',state=2)),
dict(name='item 3', attributes=dict(device_class='c2',state=3)),
dict(name='item 4', attributes=dict(device_class='c1',state=4)),
dict(name='item 5', attributes=dict(device_class='c2',state=5)),
dict(name='item 6', attributes=dict(device_class='c1',state=6)),
dict(name='item 7', attributes=dict(device_class='c2',state=7)),
]
%}
{% for device_class, entities in src | groupby("attributes.device_class") %}
{{- device_class }}: {{ entities | length }} sensors [{{ entities | map(attribute='name')|join(', ') }}]
{% endfor %}
The results seem to suggest that pre-sorting is not required:
c1: 4 sensors [item 1, item 2, item 4, item 6]
c2: 3 sensors [item 3, item 5, item 7]
I also tested it with randomized order of states, e.g.:
device_class undefined: {{ states|selectattr('attributes.device_class','undefined')|list|count }} items
{% for device_class, entities in states|shuffle|groupby('attributes.device_class','missing') -%}
{% if device_class == 'missing' -%}
{{ device_class }}: {{ entities | length }} items
{%- endif %}
{%- endfor %}
Result in my system:
device_class undefined: 683 items
missing: 683 items