Welcome to MMA and MMA SE! There are many ways to do this. Here's a "straightforward" way, where it's easy to keep track of what happens when. The procedure we'll follow is
Group all lists which start with the same two elements into lists of those lists.
Act on each group of lists to combine them in the way you want.
For the first we'll use GatherBy
with the function Take[#, 2] &
. Take[list, 2]
will produce a list consisting of the first 2 elements, and GatherBy
will compare these values and put lists with the same value in the same list. See Rearranging And Restructuring Lists for more such functions. (Equivalent to Take[#, 2] &
: #[[1;;2]] &
, Extract[{{1},{2}}]
, and more.)
If you haven't seen it, <expression involving #> &
is syntax for an anonymous function with argument slot #
. There's another equivalent syntax for anonymous functions, which is x |-> <expression involving x>
.
GatherBy[list, Take[#, 2] &]
(* Example: *)
a = {{3, 1, 0, 0, 0}, {3, 1, 1, 2, 2}, {3, 3, 1, 1, 1}, {0, 1, 1, 0, 2},
{1, 2, 0, 2, 3}, {3, 1, 2, 1, 0}, {3, 2, 2, 2, 3}, {0, 0, 2, 0, 1},
{2, 2, 0, 0, 2}, {3, 2, 1, 2, 2}}
GatherBy[a, Take[#, 2] &]
(* Out:
{{{3, 1, 0, 0, 0}, {3, 1, 1, 2, 2}, {3, 1, 2, 1, 0}}, {{3, 3, 1, 1, 1}},
{{0, 1, 1, 0, 2}}, {{1, 2, 0, 2, 3}}, {{3, 2, 2, 2, 3}, {3, 2, 1, 2, 2}},
{{0, 0, 2, 0, 1}}, {{2, 2, 0, 0, 2}}}
*)
Here we can use the fact that Mean[{a, a, a}]
is a
, and apply Mean
"across" the whole group of lists: we want to turn a group of the form {{a1, a2, a3, ...}, {b1, b2, b3, ...}, ...}
into {Mean[{a1, b1, ...}], Mean[{a2, b2, ...}], ...}
.
So we want to Map
(/@
) a function that does this to a single group over the result of GatherBy
, so as to apply it to each group. The function we want to map is one that first takes the Transpose
(which is possible since all lists in a group will be the same length) and then applies Mean
to all the lists that are elements of the resulting list. Applying Mean
to all the elements is another Map
. So we want to apply the following function to each group:
x |-> Mean /@ Transpose[x]
This is the function we want to Map
over the result of GatherBy
.
Putting it all together, let's define a function which will do this for an arbitrary list (of equal-length lists)
combineLists[list_] := (x |-> Mean /@ Transpose[x]) /@ GatherBy[list, Take[#, 2] &]
combineLists[a]
(* Out:
{{3, 1, 1, 1, 2/3}, {3, 3, 1, 1, 1}, {0, 1, 1, 0, 2}, {1, 2, 0, 2, 3},
{3, 2, 3/2, 2, 5/2}, {0, 0, 2, 0, 1}, {2, 2, 0, 0, 2}}
For familiarity with Mathematica: "threading across lists" like we do with Mean
is actually a very common operation—so common that there's a built-in called MapThread
to do something similar. For a function f
, MapThread[f, {{a1, a2, a3, ...}, {b1, b2, b3, ...}, ...}]
produces {f[a1, b1, ...], f[a2, b2, ...], ...}
. Note that Mean
, however, takes in a single list as an argument, not several different arguments. So instead of f[a1, b1, ...]
we want Mean[{a1, b1, ...}]
. There are two ways of still using MapThread
, though!
1: When defining anonymous functions, #
stands for the first argument. #2
, #3
, etc. stand for the next argument. (#1
is valid too, and is equivalent to #
.) But what if we want to include the whole sequence of arguments, no matter how long it is? Then we use ##
, and that stands for a whole Sequence
. So here we can actually just use MapThread[Mean[{##}] &, group]
. Also, MapThread
has what's called an "operator form" which is common in Mathematica: MapThread[f]
is a function that can be applied to a list, so MapThread[f][list]
is the same as MapThread[f, list]
. So we can just map MapThread[Mean[{##}] &]
:
(* Equivalent definition *)
combineLists[list_] := MapThread[Mean[{##}] &] /@ GatherBy[list, Take[#, 2] &]
2: When we write {a,b,c}
we're using syntactic sugar—it's not what's "really going on". The actual expression can be seen with FullForm
, which is an incredibly useful tool in Mathematica, as it lets you see all the information you need. {a,b,c}
is actually List[a, b, c]
. List
may as well be a function; in general, that part of the expression is called a head. (Head[{a,b,c}]
gives List
.) Note that, as in actual math, the function f[g[x, y, ...]]
is the composition of f
and g
. So too in Mathematica; composition is denoted by @*
. And what is Mean[{a,b,...}]
but Mean[List[a,b,...]]
? So we can instead simply use MapThread[Mean @* List]
in our definition:
(* Equivalent definition *)
combineLists[list_] := MapThread[Mean @* List] /@ GatherBy[list, Take[#, 2] &]
Finally, as pointed out by @kglr, we can simply use Mean[group]
instead of e.g. Mean /@ Transpose[group]
! This is because Mean
averages the elements of the group as vectors, and averaging on vectors works componentwise! As another note on how Mathematica handles operations on lists, some symbols like Plus
have the attribute Listable
. (Evaluate Attributes[Plus]
to see Listable
along with a whole lot of others. In general, attributes affect the way a function is both evaluated and pattern-matched in special, built-in ways.) This means that the Plus
operation is threaded over any lists appearing in its argument: 1 + {a,b,c}
is {1 + a, 1+ b, 1+ c}
, {a,b,c} + {x,y,z}
is {a x, b y, c z}
, etc. ("Threading" can be done manually—and over different heads than List
!—with the function Thread
.) All basic numeric functions are Listable
, such as Times
, Power
, etc. (You can make your own function Listable
with e.g. SetAttributes[f, Listable]
.) In any case, I hope the explanation using Transpose
and MapThread
was still informative! :)
A ≠ a
orB ≠ b
? Maybe the sublists are removed from the nested list? $\endgroup${{A,B,C,D,E,F},{G,H,I,J,K,L}}
$\endgroup$