# Extracting certain elements from a ragged array

For the following let me use this notation:

$$\qquad \text{data}=\{\{x_1,y_{11}\}, \{x_1,y_{12}\},\{x_1,y_{1N}\},\,\dots,\,\{x_2,y_{21}\}, \{x_2,y_{22}\},\{x_2,x_{2M}\},\,\dots\}$$,

where $$M\neq N$$. What I'm trying to find are the smallest values, $$\{\{x_1,y_{11}\}, \{x_2,y_{21}\},\dots\}$$, and the largest values, $$\{\{x_1,y_{1N}\}, \{x_2,y_{2M}\},\,\dots\}$$

As an example, expressed in the Wolfram Language:

data =
{{0.2,0.255556}, {0.2,0.411111}, {0.2,0.566667}, {0.2,0.722222}, {0.2,0.877778}, {0.2,1.03333}, {0.2,1.18889}, {0.2,1.34444}, {0.2,1.5},
{0.4,0.411111}, {0.4,0.566667}, {0.4,0.722222}, {0.4,0.877778}, {0.4,1.03333}, {0.4,1.18889}, {0.4,1.34444}, {0.4,1.5},
{0.6,0.566667}, {0.6,0.722222}, {0.6,0.877778}, {0.6,1.03333}, {0.6,1.18889}, {0.6,1.34444}, {0.6,1.5},
{0.8,0.722222}, {0.8,0.877778}, {0.8,1.03333}, {0.8,1.18889}, {0.8,1.34444}, {0.8,1.5},
{1.,1.03333}, {1.,1.18889}, {1.,1.34444}, {1.,1.5},{1.2,1.18889}, {1.2,1.34444}, {1.2,1.5}, {1.4,1.5}}

SmallestValues =
{{0.2, 0.255556}, {0.4, 0.411111}, {0.6, 0.566667}, {0.8, 0.722222}, {1., 1.03333}, {1.2, 1.18889}, {1.4, 1.5}}
BiggestValues =
{{0.2, 1.5}, {0.4, 1.5}, {0.6, 1.5}, {0.8, 1.5}, {1., 1.5}, {1.2, 1.5}, {1.4, 1.5}}

My problem is that I honestly can't think of a way to get this done in Mathematica. I'm used to python coding and there I would probably change the type from a list to something like a matrix and then manipulate this using for-loops, but this seems rather inefficient and is presumably not necessary here.

• Do you mean "from" instead of "form"? May 3, 2019 at 18:52

Join @@ Values @ GroupBy[data, First, MinimalBy[Last]]

{{0.2, 0.255556}, {0.4, 0.411111}, {0.6, 0.566667}, {0.8,   0.722222}, {1., 1.03333}, {1.2, 1.18889}, {1.4, 1.5}}

Join @@ Values @ GroupBy[data, First, MaximalBy[Last]]

{{0.2, 1.5}, {0.4, 1.5}, {0.6, 1.5}, {0.8, 1.5}, {1., 1.5}, {1.2,   1.5}, {1.4, 1.5}}

If your data is guaranteed to be ordered consecutively for the same $$x$$, then:

groups = SplitBy[data, #[[1]] &];
SmallestValues = groups[[All, 1]];
BiggestValues = groups[[All, -1]];

If not, you can look into GroupBy function and the code will be similar.

This works even if the data are out of order:

g = Gather[Sort[data], #1[[1]] == #2[[1]] &];

Map[Sort, g][[All, 1]]

{{0.2, 0.255556}, {0.4, 0.411111}, {0.6, 0.566667}, {0.8, 0.722222}, {1., 1.03333}, {1.2, 1.18889}, {1.4, 1.5}}

Map[Sort, g][[All, -1]]

{{0.2, 1.5}, {0.4, 1.5}, {0.6, 1.5}, {0.8, 1.5}, {1., 1.5}, {1.2, 1.5}, {1.4, 1.5}}

You may compose a few functions together to collect your results. There are a few ways to compose the functions.

With Query and RightComposition

{smallest, biggest} =
Query[GroupBy[First -> Last] /* KeyValueMap[Thread[{##}] &] /* Transpose, MinMax]@data

With Map and Prefix.

{smallest, biggest} =
Transpose@KeyValueMap[Thread[{##}] &, MinMax /@ GroupBy[First -> Last]@data]

Both give

{
{{0.2,0.255556},{0.4,0.411111},{0.6,0.566667},{0.8,0.722222},{1.,1.03333},{1.2,1.18889},{1.4,1.5}},
{{0.2,1.5},{0.4,1.5},{0.6,1.5},{0.8,1.5},{1.,1.5},{1.2,1.5},{1.4,1.5}}
}

Hope this helps.

KeyValueMap[List]@GroupBy[data, First -> Last, Min]

{{0.2, 0.255556}, {0.4, 0.411111}, {0.6, 0.566667}, {0.8, 0.722222}, {1., 1.03333}, {1.2, 1.18889}, {1.4, 1.5}}

KeyValueMap[List]@GroupBy[data, First -> Last, Max]

{{0.2, 1.5}, {0.4, 1.5}, {0.6, 1.5}, {0.8, 1.5}, {1., 1.5}, {1.2, 1.5}, {1.4, 1.5}}

Both at once:

KeyValueMap[List]@GroupBy[data, First -> Last, MinMax]

{{0.2, {0.255556, 1.5}}, {0.4, {0.411111, 1.5}}, {0.6, {0.566667, 1.5}}, {0.8, {0.722222, 1.5}}, {1., {1.03333, 1.5}}, {1.2, {1.18889, 1.5}}, {1.4, {1.5, 1.5}}}

You can leave out the KeyValueMap[List]@ part and only use the GroupBy part if you want to have an association instead of a list.

mm = Merge[MinMax] @ MapApply[#1 -> {##2} &] @ data;

AssociationThread[{"Min", "Max"} -> #] & /@ mm // Dataset

Using SortBy and SplitBy:

data = {{0.2, 0.255556}, {0.2, 0.411111}, {0.2, 0.566667}, {0.2,
0.722222}, {0.2, 0.877778}, {0.2, 1.03333}, {0.2, 1.18889}, {0.2,
1.34444}, {0.2, 1.5}, {0.4, 0.411111}, {0.4, 0.566667}, {0.4,
0.722222}, {0.4, 0.877778}, {0.4, 1.03333}, {0.4, 1.18889}, {0.4,
1.34444}, {0.4, 1.5}, {0.6, 0.566667}, {0.6, 0.722222}, {0.6,
0.877778}, {0.6, 1.03333}, {0.6, 1.18889}, {0.6, 1.34444}, {0.6,
1.5}, {0.8, 0.722222}, {0.8, 0.877778}, {0.8, 1.03333}, {0.8,
1.18889}, {0.8, 1.34444}, {0.8, 1.5}, {1., 1.03333}, {1.,
1.18889}, {1., 1.34444}, {1., 1.5}, {1.2, 1.18889}, {1.2,
1.34444}, {1.2, 1.5}, {1.4, 1.5}};

t1 = data // SortBy[#, First] & // SplitBy[#, First] & // Map[First]
t2 = data // SortBy[#, First] & // SplitBy[#, First] & // Map[Last]

Prepend[
Join[t1, t2, 2][[All, {1, 2, 4}]]
, {"First", "Min", "Max"}] //
Grid[#
, Alignment -> {Left, Left, Left, Center}
, ItemSize -> {{3, 5, 2}, 1}
] &

An alternative method using GatherBy:

(x |-> {First /@ x, Last /@ x})@GatherBy[data, First]

Both give:

{
{{0.2,0.255556},{0.4,0.411111},{0.6,0.566667},{0.8,0.722222},{1.,1.03333},{1.2,1.18889},{1.4,1.5}},
{{0.2,1.5},{0.4,1.5},{0.6,1.5},{0.8,1.5},{1.,1.5},{1.2,1.5},{1.4,1.5}}
}