Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for columnar datasets: arquero and quarto #1324

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@types/node": "^18.11.13",
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"arquero": "^5",
"canvas": "^2.0.0",
"d3-geo-projection": "^4.0.0",
"eslint": "^8.16.0",
Expand Down
35 changes: 33 additions & 2 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const objectToString = Object.prototype.toString;
export function valueof(data, value, type) {
const valueType = typeof value;
return valueType === "string"
? maybeTypedMap(data, field(value), type)
? typeof data?.array === "function"
? data.array(value, type)
: maybeTypedMap(data, field(value), type)
: valueType === "function"
? maybeTypedMap(data, value, type)
: valueType === "number" || value instanceof Date || valueType === "boolean"
Expand Down Expand Up @@ -124,7 +126,36 @@ export function keyword(input, name, allowed) {

// Promotes the specified data to an array as needed.
export function arrayify(data) {
return data == null || data instanceof Array || data instanceof TypedArray ? data : Array.from(data);
return data == null || data instanceof Array || data instanceof TypedArray ? data : maybeColumnar(data);
}

function maybeColumnar(data) {
if (isObject(data)) {
// arquero table (duck typing)
if (typeof data.array === "function" && typeof data.totalRows === "function") {
return {
length: data.totalRows(),
array: (x, type) => data.array(x, type),
[Symbol.iterator]: function* () {
yield* data;
}
};
}
// quarto's dataframe (object of arrays)
const columns = Object.keys(data);
const lengths = columns.map((c) => data[c]?.length);
if (new Set(lengths).size === 1 && lengths[0] !== undefined) {
const length = lengths[0];
return {
length,
array: (x, type) => maybeTypedArrayify(data[x], type),
[Symbol.iterator]: function* () {
for (let i = 0; i < length; ++i) yield Object.fromEntries(columns.map((c) => [c, data[c][i]]));
}
};
}
}
return Array.from(data);
}

// An optimization of type.from(values, f): if the given values are already an
Expand Down
4,622 changes: 4,622 additions & 0 deletions test/output/diamondsBoxplotArquero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4,622 changes: 4,622 additions & 0 deletions test/output/diamondsBoxplotColumnar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions test/output/travelersCovidDropArquero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions test/output/travelersCovidDropColumnar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion test/plots/diamonds-boxplot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import * as aq from "arquero";

export default async function () {
export async function diamondsBoxplot() {
const diamonds = await d3.csv("data/diamonds.csv", d3.autoType);
return Plot.plot({
marks: [Plot.boxX(diamonds, {x: "price", y: "clarity", sort: {y: "x"}})]
});
}

export async function diamondsBoxplotArquero() {
const diamonds = aq.from(await d3.csv("data/diamonds.csv", d3.autoType));
return Plot.plot({
marks: [Plot.boxX(diamonds, {x: "price", y: "clarity", sort: {y: "x"}})]
});
}

export async function diamondsBoxplotColumnar() {
const raw = await d3.csv("data/diamonds.csv", d3.autoType);
const diamonds = {price: Plot.valueof(raw, "price"), clarity: Plot.valueof(raw, "clarity")};
return Plot.plot({
marks: [Plot.boxX(diamonds, {x: "price", y: "clarity", sort: {y: "x"}})]
});
}
4 changes: 2 additions & 2 deletions test/plots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export {default as d3Survey2015Comfort} from "./d3-survey-2015-comfort.js";
export {default as d3Survey2015Why} from "./d3-survey-2015-why.js";
export {default as darkerDodge} from "./darker-dodge.js";
export {default as decathlon} from "./decathlon.js";
export {default as diamondsBoxplot} from "./diamonds-boxplot.js";
export {default as diamondsCaratPrice} from "./diamonds-carat-price.js";
export {default as diamondsCaratPriceDots} from "./diamonds-carat-price-dots.js";
export {default as diamondsCaratSampling} from "./diamonds-carat-sampling.js";
Expand Down Expand Up @@ -242,7 +241,6 @@ export {default as stocksIndex} from "./stocks-index.js";
export {default as thisIsJustToSay} from "./this-is-just-to-say.js";
export {default as trafficHorizon} from "./traffic-horizon.js";
export {default as travelersYearOverYear} from "./travelers-year-over-year.js";
export {default as travelersCovidDrop} from "./travelers-covid-drop.js";
export {default as uniformRandomDifference} from "./uniform-random-difference.js";
export {default as untypedDateBin} from "./untyped-date-bin.js";
export {default as usCongressAge} from "./us-congress-age.js";
Expand Down Expand Up @@ -286,6 +284,7 @@ export * from "./autoplot.js";
export * from "./axis-labels.js";
export * from "./bigint.js";
export * from "./bin-1m.js";
export * from "./diamonds-boxplot.js";
export * from "./electricity-demand.js";
export * from "./federal-funds.js";
export * from "./frame.js";
Expand All @@ -306,4 +305,5 @@ export * from "./raster-vapor.js";
export * from "./raster-walmart.js";
export * from "./seattle-temperature-amplitude.js";
export * from "./text-overflow.js";
export * from "./travelers-covid-drop.js";
export * from "./volcano.js";
48 changes: 47 additions & 1 deletion test/plots/travelers-covid-drop.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import * as aq from "arquero";

export default async function () {
export async function travelersCovidDrop() {
const travelers = await d3.csv("data/travelers.csv", d3.autoType);
return Plot.plot({
width: 960,
Expand All @@ -20,3 +21,48 @@ export default async function () {
]
});
}

export async function travelersCovidDropArquero() {
const travelers = aq.from(await d3.csv("data/travelers.csv", d3.autoType));
return Plot.plot({
width: 960,
y: {
grid: true,
zero: true,
label: "↓ Drop in passenger throughput (2020 vs. 2019)",
tickFormat: "%"
},
marks: [
Plot.lineY(travelers, {x: "date", y: (d) => d.current / d.previous - 1, strokeWidth: 0.25, curve: "step"}),
Plot.lineY(
travelers,
Plot.windowY({x: "date", y: (d) => d.current / d.previous - 1, k: 7, strict: true, stroke: "steelblue"})
)
]
});
}

export async function travelersCovidDropColumnar() {
const raw = await d3.csv("data/travelers.csv", d3.autoType);
const travelers = {
date: Plot.valueof(raw, "date"),
current: Plot.valueof(raw, "current"),
previous: Plot.valueof(raw, "previous")
};
return Plot.plot({
width: 960,
y: {
grid: true,
zero: true,
label: "↓ Drop in passenger throughput (2020 vs. 2019)",
tickFormat: "%"
},
marks: [
Plot.lineY(travelers, {x: "date", y: (d) => d.current / d.previous - 1, strokeWidth: 0.25, curve: "step"}),
Plot.lineY(
travelers,
Plot.windowY({x: "date", y: (d) => d.current / d.previous - 1, k: 7, strict: true, stroke: "steelblue"})
)
]
});
}
Loading