Skip to content

Commit

Permalink
Merge pull request #2540 from owid/feat-add-duplicate-transform
Browse files Browse the repository at this point in the history
feat(explorer): add duplicate transform
  • Loading branch information
sophiamersmann authored Aug 15, 2023
2 parents 8d4a150 + 75a8eb6 commit 502dc82
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 30 deletions.
38 changes: 19 additions & 19 deletions explorer/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ export class Explorer
[...ySlugs.split(" "), xSlug, colorSlug, sizeSlug].filter(identity)
) as string[]

// find all variables the the transformed columns depend on and add them to the dimensions array
// find all variables that the transformed columns depend on and add them to the dimensions array
if (uniqueSlugsInGrapherRow.length) {
const baseVariableIds = uniq(
uniqueSlugsInGrapherRow.flatMap((slug) =>
Expand Down Expand Up @@ -577,24 +577,6 @@ export class Explorer

let grapherTable = grapher.inputTable

// update column definitions with manually provided properties
grapherTable = grapherTable.updateDefs((def: OwidColumnDef) => {
const manuallyProvidedDef =
this.columnDefsWithoutTableSlugByIdOrSlug[def.slug] ?? {}
const mergedDef = { ...def, ...manuallyProvidedDef }

// update display properties
mergedDef.display = mergedDef.display ?? {}
if (manuallyProvidedDef.name)
mergedDef.display.name = manuallyProvidedDef.name
if (manuallyProvidedDef.unit)
mergedDef.display.unit = manuallyProvidedDef.unit
if (manuallyProvidedDef.shortUnit)
mergedDef.display.shortUnit = manuallyProvidedDef.shortUnit

return mergedDef
})

// add transformed (and intermediate) columns to the grapher table
if (uniqueSlugsInGrapherRow.length) {
const allColumnSlugs = uniq(
Expand All @@ -613,6 +595,24 @@ export class Explorer
grapherTable = grapherTable.appendColumns(requiredColumnDefs)
}

// update column definitions with manually provided properties
grapherTable = grapherTable.updateDefs((def: OwidColumnDef) => {
const manuallyProvidedDef =
this.columnDefsWithoutTableSlugByIdOrSlug[def.slug] ?? {}
const mergedDef = { ...def, ...manuallyProvidedDef }

// update display properties
mergedDef.display = mergedDef.display ?? {}
if (manuallyProvidedDef.name)
mergedDef.display.name = manuallyProvidedDef.name
if (manuallyProvidedDef.unit)
mergedDef.display.unit = manuallyProvidedDef.unit
if (manuallyProvidedDef.shortUnit)
mergedDef.display.shortUnit = manuallyProvidedDef.shortUnit

return mergedDef
})

this.setGrapherTable(grapherTable)
}

Expand Down
36 changes: 36 additions & 0 deletions packages/@ourworldindata/core-table/src/CoreTable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,42 @@ popChange,Pop change,percentChange time country population 2`
).toEqual(expected.slice(1))
})
})

describe("copies data & metadata for duplicate transform", () => {
const table = new CoreTable(
`country,population
iceland,1
iceland,2
france,50
france,60`,
[
{
slug: "country",
name: "Region",
},
{
slug: "population",
name: "Population in 2020",
type: ColumnTypeNames.Integer,
},
{
slug: "pop2",
transform: "duplicate population",
},
]
)
const expected = [1, 2, 50, 60]
it("runs transforms correctly", () => {
expect(table.get("pop2").valuesIncludingErrorValues).toEqual(
expected
)

expect(table.get("pop2").def.name).toEqual("Population in 2020")
expect(table.get("pop2").def.type).toEqual(
ColumnTypeNames.Integer
)
})
})
})

it("can create an empty table", () => {
Expand Down
15 changes: 14 additions & 1 deletion packages/@ourworldindata/core-table/src/CoreTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
DroppedForTesting,
} from "./ErrorValues.js"
import { OwidTableSlugs } from "./OwidTableConstants.js"
import { applyTransforms } from "./Transforms.js"
import { applyTransforms, extractTransformNameAndParams } from "./Transforms.js"

interface AdvancedOptions {
tableDescription?: string
Expand Down Expand Up @@ -110,6 +110,19 @@ export class CoreTable<
? columnDefinitionsFromInput<COL_DEF_TYPE>(inputColumnDefs)
: inputColumnDefs

// Column definitions with a "duplicate" transform are merged with the column definition of the specified source column
this.inputColumnDefs = this.inputColumnDefs.map((def) => {
if (!def.transform) return def
const transform = extractTransformNameAndParams(def.transform)
if (transform?.transformName !== "duplicate") return def

const sourceSlug = transform.params[0]
const sourceDef = this.inputColumnDefs.find(
(def) => def.slug === sourceSlug
)
return { ...sourceDef, ...def }
})

// If any values were passed in, copy those to column store now and then remove them from column definitions.
// todo: remove values property entirely? may be an anti-pattern.
this.inputColumnDefs = this.inputColumnDefs.map((def) => {
Expand Down
35 changes: 25 additions & 10 deletions packages/@ourworldindata/core-table/src/Transforms.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { flatten, ColumnSlug, zip, uniq } from "@ourworldindata/utils"
import {
flatten,
ColumnSlug,
zip,
uniq,
cloneDeep,
} from "@ourworldindata/utils"
import { CoreColumnStore, Time, CoreValueType } from "./CoreTableConstants.js"
import { CoreColumnDef } from "./CoreColumnDef.js"
import {
Expand Down Expand Up @@ -386,20 +392,29 @@ const asPercentageOf: Transform = {
.map((num: any) => (typeof num === "number" ? 100 * num : num)),
}

const duplicate: Transform = {
params: [{ type: TransformParamType.DataSlug }],
fn: (
columnStore: CoreColumnStore,
columnSlug: ColumnSlug
): CoreValueType[] => cloneDeep(columnStore[columnSlug]),
}

const availableTransforms: Record<string, Transform> = {
asPercentageOf: asPercentageOf,
timeSinceEntityExceededThreshold: timeSinceEntityExceededThreshold,
divideBy: divideBy,
rollingAverage: rollingAverage,
percentChange: percentChange,
multiplyBy: multiplyBy,
subtract: subtract,
where: where,
asPercentageOf,
timeSinceEntityExceededThreshold,
divideBy,
rollingAverage,
percentChange,
multiplyBy,
subtract,
where,
duplicate,
} as const

export const AvailableTransforms = Object.keys(availableTransforms)

const extractTransformNameAndParams = (
export const extractTransformNameAndParams = (
transform: string
): { transformName: string; params: string[] } | undefined => {
const words = transform.split(" ")
Expand Down

0 comments on commit 502dc82

Please sign in to comment.