Regarding efficiency and performance, you could improve it by doing some of these successive Enum operations in one pass, which should avoid building intermediate lists and walk them twice.
filter |> map could be re-implemented with a comprehension:
|> Enum.filter(fn {key, _value} -> key >= end_bucket end)
|> Enum.map(fn {_age, count} -> count end)
could be
for {age, _count} <- age_frequencies, age >= end_bucket, do: count
map |> map should typically be avoided, since you can do it directly in one pass:
|> Enum.map(fn {_key, value} -> Enum.map(value, fn {_x, e} -> e end) end)
|> Enum.map(fn p -> Enum.sum(p) end)
could be
|> Enum.map(fn {_key, value} -> Enum.map(value, fn {_x, e} -> e end) |> Enum.sum() end)
and even map |> sum could be replaced by Enum/reduce/3 here (although this one might be slightly less readable):
|> Enum.map(fn {_key, value} -> Enum.reduce(value, 0, fn {_x, e}, acc -> e + acc end) end)
Credo has some checks like MapMap, FilterFilter, MapJoin… to help detect some of these patterns.
I didn’t mention Stream, since it also comes with some overhead and would probably not improve performance here except if you are working with large lists.






















