Skip to content

Commit 425b6eb

Browse files
authored
Merge pull request #2998 from finos/max-min-by
Add `max by` and `min by` aggregates
2 parents 43bd62c + 0c979d5 commit 425b6eb

16 files changed

Lines changed: 267 additions & 30 deletions

File tree

cpp/perspective/src/cpp/aggspec.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ t_aggspec::agg_str() const {
188188
case AGGTYPE_LAST_VALUE: {
189189
return "last_value";
190190
}
191+
case AGGTYPE_MAX_BY: {
192+
return "max_by";
193+
}
194+
case AGGTYPE_MIN_BY: {
195+
return "min_by";
196+
}
191197
case AGGTYPE_MAX: {
192198
return "max";
193199
}
@@ -358,6 +364,8 @@ t_aggspec::get_output_specs(const t_schema& schema) const {
358364
case AGGTYPE_OR:
359365
case AGGTYPE_LAST_VALUE:
360366
case AGGTYPE_MAX:
367+
case AGGTYPE_MAX_BY:
368+
case AGGTYPE_MIN_BY:
361369
case AGGTYPE_MIN:
362370
case AGGTYPE_HIGH_WATER_MARK:
363371
case AGGTYPE_LOW_WATER_MARK:

cpp/perspective/src/cpp/base.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,12 @@ str_to_aggtype(const std::string& str) {
531531
if (str == "last" || str == "last_value") {
532532
return t_aggtype::AGGTYPE_LAST_VALUE;
533533
}
534+
if (str == "max_by" || str == "max by") {
535+
return t_aggtype::AGGTYPE_MAX_BY;
536+
}
537+
if (str == "min_by" || str == "min by") {
538+
return t_aggtype::AGGTYPE_MIN_BY;
539+
}
534540
if (str == "max") {
535541
return t_aggtype::AGGTYPE_MAX;
536542
}

cpp/perspective/src/cpp/config.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ t_config::setup(
221221
case AGGTYPE_PY_AGG:
222222
case AGGTYPE_MIN:
223223
case AGGTYPE_MAX:
224+
case AGGTYPE_MAX_BY:
225+
case AGGTYPE_MIN_BY:
224226
case AGGTYPE_SUM_NOT_NULL:
225227
case AGGTYPE_SUM_ABS:
226228
case AGGTYPE_ABS_SUM:

cpp/perspective/src/cpp/extract_aggregate.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ extract_aggregate(
6969
case AGGTYPE_LAST_VALUE:
7070
case AGGTYPE_LAST_MINUS_FIRST:
7171
case AGGTYPE_MAX:
72+
case AGGTYPE_MAX_BY:
73+
case AGGTYPE_MIN_BY:
7274
case AGGTYPE_MIN:
7375
case AGGTYPE_HIGH_WATER_MARK:
7476
case AGGTYPE_LOW_WATER_MARK:

cpp/perspective/src/cpp/sparse_tree.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,35 @@ t_stree::update_agg_table(
15921592
new_value.set(*std::max_element(values.begin(), values.end()));
15931593
dst->set_scalar(dst_ridx, new_value);
15941594
} break;
1595+
case AGGTYPE_MAX_BY: {
1596+
t_tscalar dst_scalar = dst->get_scalar(dst_ridx);
1597+
old_value.set(dst_scalar);
1598+
auto pkeys = get_pkeys(nidx);
1599+
std::vector<t_tscalar> values;
1600+
read_column_from_gstate(
1601+
gstate,
1602+
expression_master_table,
1603+
spec.get_dependencies()[1].name(),
1604+
pkeys,
1605+
values
1606+
);
1607+
1608+
const auto max_row_idx = std::distance(
1609+
values.begin(),
1610+
std::max_element(values.begin(), values.end())
1611+
);
1612+
1613+
read_column_from_gstate(
1614+
gstate,
1615+
expression_master_table,
1616+
spec.get_dependencies()[0].name(),
1617+
pkeys,
1618+
values
1619+
);
1620+
1621+
new_value.set(values.at(max_row_idx));
1622+
dst->set_scalar(dst_ridx, new_value);
1623+
} break;
15951624
case AGGTYPE_MIN: {
15961625
t_tscalar dst_scalar = dst->get_scalar(dst_ridx);
15971626
old_value.set(dst_scalar);
@@ -1608,6 +1637,35 @@ t_stree::update_agg_table(
16081637
new_value.set(*std::min_element(values.begin(), values.end()));
16091638
dst->set_scalar(dst_ridx, new_value);
16101639
} break;
1640+
case AGGTYPE_MIN_BY: {
1641+
t_tscalar dst_scalar = dst->get_scalar(dst_ridx);
1642+
old_value.set(dst_scalar);
1643+
auto pkeys = get_pkeys(nidx);
1644+
std::vector<t_tscalar> values;
1645+
read_column_from_gstate(
1646+
gstate,
1647+
expression_master_table,
1648+
spec.get_dependencies()[1].name(),
1649+
pkeys,
1650+
values
1651+
);
1652+
1653+
const auto min_row_idx = std::distance(
1654+
values.begin(),
1655+
std::min_element(values.begin(), values.end())
1656+
);
1657+
1658+
read_column_from_gstate(
1659+
gstate,
1660+
expression_master_table,
1661+
spec.get_dependencies()[0].name(),
1662+
pkeys,
1663+
values
1664+
);
1665+
1666+
new_value.set(values.at(min_row_idx));
1667+
dst->set_scalar(dst_ridx, new_value);
1668+
} break;
16111669
case AGGTYPE_HIGH_WATER_MARK: {
16121670
t_tscalar src_scalar = src->get_scalar(src_ridx);
16131671
t_tscalar dst_scalar = dst->get_scalar(dst_ridx);

cpp/perspective/src/cpp/view_config.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
1111
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
1212

13+
#include "perspective/base.h"
1314
#include <perspective/view_config.h>
1415

1516
#include <utility>
@@ -343,6 +344,12 @@ t_view_config::fill_aggspecs(const std::shared_ptr<t_schema>& schema) {
343344
if (col.at(0) == "weighted mean") {
344345
dependencies.emplace_back(col.at(1), DEPTYPE_COLUMN);
345346
agg_type = AGGTYPE_WEIGHTED_MEAN;
347+
} else if (col.at(0) == "max by") {
348+
dependencies.emplace_back(col.at(1), DEPTYPE_COLUMN);
349+
agg_type = AGGTYPE_MAX_BY;
350+
} else if (col.at(0) == "min by") {
351+
dependencies.emplace_back(col.at(1), DEPTYPE_COLUMN);
352+
agg_type = AGGTYPE_MIN_BY;
346353
} else {
347354
agg_type = str_to_aggtype(col.at(0));
348355
}
@@ -427,6 +434,12 @@ t_view_config::make_aggspec(
427434
if (aggregate.at(0) == "weighted mean") {
428435
dependencies.emplace_back(aggregate.at(1), DEPTYPE_COLUMN);
429436
agg_type = AGGTYPE_WEIGHTED_MEAN;
437+
} else if (aggregate.at(0) == "max by") {
438+
dependencies.emplace_back(aggregate.at(1), DEPTYPE_COLUMN);
439+
agg_type = AGGTYPE_MAX_BY;
440+
} else if (aggregate.at(0) == "min by") {
441+
dependencies.emplace_back(aggregate.at(1), DEPTYPE_COLUMN);
442+
agg_type = AGGTYPE_MIN_BY;
430443
} else {
431444
agg_type = str_to_aggtype(aggregate.at(0));
432445
}

cpp/perspective/src/include/perspective/base.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ enum t_aggtype {
278278
AGGTYPE_LOW_WATER_MARK,
279279
AGGTYPE_MAX,
280280
AGGTYPE_MIN,
281+
AGGTYPE_MAX_BY,
282+
AGGTYPE_MIN_BY,
281283
AGGTYPE_HIGH_MINUS_LOW,
282284
AGGTYPE_UDF_COMBINER,
283285
AGGTYPE_UDF_REDUCER,

packages/perspective-jupyterlab/build.mjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,7 @@ async function build_all() {
136136
});
137137

138138
const pkg = JSON.parse(fs.readFileSync("../../package.json").toString());
139-
const version = pkg.version.replace(/-(rc|alpha|beta)\.\d+/, (x) =>
140-
x.replace("-", "").replace(".", "")
141-
);
142-
143-
const labext_dest = `../../rust/perspective-python/perspective_python-${version}.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab`;
139+
const labext_dest = `../../rust/perspective-python/perspective_python-${pkg.version}.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab`;
144140
await cpy(["dist/cjs/**/*"], labext_dest);
145141
}
146142

rust/perspective-client/src/rust/config/aggregates.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,17 +187,25 @@ impl FromStr for SingleAggregate {
187187
}
188188
}
189189

190-
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, TS)]
190+
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, TS)]
191191
#[serde()]
192192
pub enum MultiAggregate {
193193
#[serde(rename = "weighted mean")]
194194
WeightedMean,
195+
196+
#[serde(rename = "max by")]
197+
MaxBy,
198+
199+
#[serde(rename = "min by")]
200+
MinBy,
195201
}
196202

197203
impl Display for MultiAggregate {
198204
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199205
match self {
200206
MultiAggregate::WeightedMean => write!(f, "weighted mean"),
207+
MultiAggregate::MaxBy => write!(f, "max by"),
208+
MultiAggregate::MinBy => write!(f, "min by"),
201209
}
202210
}
203211
}
@@ -222,6 +230,8 @@ impl Display for Aggregate {
222230
Self::MultiAggregate(MultiAggregate::WeightedMean, x) => {
223231
write!(fmt, "weighted mean by {}", x)?
224232
},
233+
Self::MultiAggregate(MultiAggregate::MaxBy, x) => write!(fmt, "max by {}", x)?,
234+
Self::MultiAggregate(MultiAggregate::MinBy, x) => write!(fmt, "min by {}", x)?,
225235
};
226236
Ok(())
227237
}
@@ -234,6 +244,10 @@ impl FromStr for Aggregate {
234244
Ok(
235245
if let Some(stripped) = input.strip_prefix("weighted mean by ") {
236246
Self::MultiAggregate(MultiAggregate::WeightedMean, stripped.to_owned())
247+
} else if let Some(stripped) = input.strip_prefix("max by ") {
248+
Self::MultiAggregate(MultiAggregate::MaxBy, stripped.to_owned())
249+
} else if let Some(stripped) = input.strip_prefix("min by ") {
250+
Self::MultiAggregate(MultiAggregate::MinBy, stripped.to_owned())
237251
} else {
238252
Self::SingleAggregate(SingleAggregate::from_str(input)?)
239253
},

rust/perspective-js/test/js/aggregates.spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,58 @@ const std = (nums) => {
834834
await table.delete();
835835
});
836836

837+
test("max_by", async () => {
838+
const data = {
839+
a: [1, 2, 3, 4, 5, 6, 7, 8],
840+
b: [0, 1, 0, 1, 0, 1, 0, 1],
841+
c: ["h", "g", "f", "e", "d", "c", "b", "a"],
842+
};
843+
844+
const table = await perspective.table(data);
845+
const view = await table.view({
846+
columns: ["c"],
847+
aggregates: {
848+
c: ["max by", "a"],
849+
},
850+
group_by: ["b"],
851+
});
852+
853+
const cols = await view.to_columns();
854+
expect(cols).toEqual({
855+
__ROW_PATH__: [[], [0], [1]],
856+
c: ["a", "b", "a"],
857+
});
858+
859+
await view.delete();
860+
await table.delete();
861+
});
862+
863+
test("min_by", async () => {
864+
const data = {
865+
a: [1, 2, 3, 4, 5, 6, 7, 8],
866+
b: [0, 1, 0, 1, 0, 1, 0, 1],
867+
c: ["h", "g", "f", "e", "d", "c", "b", "a"],
868+
};
869+
870+
const table = await perspective.table(data);
871+
const view = await table.view({
872+
columns: ["c"],
873+
aggregates: {
874+
c: ["min by", "a"],
875+
},
876+
group_by: ["b"],
877+
});
878+
879+
const cols = await view.to_columns();
880+
expect(cols).toEqual({
881+
__ROW_PATH__: [[], [0], [1]],
882+
c: ["h", "h", "g"],
883+
});
884+
885+
await view.delete();
886+
await table.delete();
887+
});
888+
837889
test("high with count and partial update", async () => {
838890
const table = await perspective.table(
839891
{

0 commit comments

Comments
 (0)