Public
Edited
Oct 23, 2023
2 forks
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
stimuli = debiasMeans(
configs.map((d, i) => {
const ys = dataGenerator(...d.noise, d.seed, d.flip, n, true);
const highNoiseYs = dataGenerator(
lowNoise,
highNoise,
d.seed,
d.flip,
n,
true
);

const data = xSequence.map((x) => ({ x, y: ys[x] }));
const highNoiseData = xSequence.map((x) => ({ x, y: highNoiseYs[x] }));

return {
index: d.index,
seed: d.seed,
noise: d.noise,
flip: d.flip,
type: "line",
data,
lineData: data,
highNoiseData
};
})
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
seeds = [1, 2, 3, 17, 18, 13, 14, 8, 15, 16, 11, 19]
Insert cell
configs = {
const configs = [];
let index = 0;
seeds.forEach((seed) =>
[mediumNoise, highNoise].forEach((noise) =>
[false, true].forEach((flip) =>
configs.push({
index: index++,
type: "line",
noise: [lowNoise, noise],
seed,
flip
})
)
)
);
return configs;
}
Insert cell
Insert cell
Insert cell
// debias the low-noise data to have the same mean as the high-noise data
function debiasMeans(data) {
return data.map((stimulus) => {
if (stimulus.noise[1] != highNoise) {
const lowNoiseData = stimulus.lineData.map((d) => d.y);
const highNoiseData = stimulus.highNoiseData.map((d) => d.y);
let fix = null;

if (stimulus.flip) {
const factor =
(1 - d3.mean(highNoiseData)) / (1 - d3.mean(lowNoiseData));

fix = (d) => ({
...d,
y: 1 - (1 - d.y) * factor
});
} else {
const factor = d3.mean(highNoiseData) / d3.mean(lowNoiseData);

fix = (d) => ({
...d,
y: d.y * factor
});
}

return {
...stimulus,
data: stimulus.data.map(fix),
lineData: stimulus.lineData.map(fix)
};
} else {
return stimulus;
}
});
}
Insert cell
// apply the scale of the high-noise data to the low-noise data. Almost the same as debiasing the means (but disable scaling in data generation) but not exaclty since noise changes isn't exactly balanced.
function scaleData(data) {
return data.map((stimulus) => {
const highNoiseYs = stimulus.highNoiseData.map((d) => d.y);
const scale = d3.scaleLinear().domain(d3.extent(highNoiseYs));

return {
...stimulus,
data: stimulus.data.map((d) => ({
...d,
y: scale(d.y)
})),
lineData: stimulus.lineData.map((d) => ({
...d,
y: scale(d.y)
}))
};
});
}
Insert cell
function dataGenerator(lowNoise, highNoise, seed, flip, n, scaleData) {
let rand = random(seed);

// https://github.com/d3/d3-random#randomNormal
const randNormal = d3.randomNormal.source(rand)(mu, sigma);

const start = 1;

let value = start;

const motion = fc
.randomGeometricBrownianMotion()
.mu(mu)
.sigma(sigma)
.period(1)
.steps(n + smoothing)
.random(randNormal)(start);

const sequence = movingAverage(motion, smoothing).slice(smoothing);

const scale = d3.scaleLinear().domain(d3.extent(sequence));

const baseSequence = sequence.map(scale);

// reset random to follow implementation in https://observablehq.com/d/2dcf91fdecdd5ff2
rand = random(seed + 1);

let out = d3.range(n).map((i) => {
const base = baseSequence[i];
const noise = lowNoise * (1 - base) + highNoise * base;
return base + (rand() - 0.5) * noise;
});

if (scaleData) {
const scale2 = d3.scaleLinear().domain(d3.extent(out));
out = out.map(scale2);
}

if (flip) {
out = out.map((d) => 1 - d);
}

return out;
}
Insert cell
Insert cell
function makeView(d, opts) {
const { w, h, showLine, showMiddle } = {
w: 500,
h: 200,
showLine: false,
showMiddle: false,
...opts
};

return {
title: title(d),
width: w,
height: h,
data: {
values: d.data
},
layer: [
{
mark: d.type === "line" ? "line" : "circle",
encoding: {
x: {
field: "x",
type: "quantitative",
title: "Time",
axis: null,
scale: { nice: false }
},
y: {
field: "y",
type: "quantitative",
scale: { domain: [0, 1] },
title: "Value",
axis: null
}
}
},
{
mark: {
type: "rule",
color: "firebrick"
},
encoding: {
y: {
field: "y",
aggregate: "mean"
}
}
},
...(showMiddle
? [
{
mark: {
type: "rule",
color: "lightgray"
},
encoding: {
y: {
datum: 0.5
}
}
}
]
: []),
...((d.type === "point_arc" || d.type === "point") && showLine
? [
{
data: {
values: d.lineData
},
mark: {
type: "line",
opacity: 0.5,
size: 0.5
},
encoding: {
x: { field: "x", type: "quantitative" },
y: {
field: "y",
type: "quantitative"
}
}
}
]
: [])
]
};
}
Insert cell
Insert cell
function title(d) {
return `Seed: ${d.seed}, Type: ${d.type}, Noise: ${d.noise[0]} to ${d.noise[1]}, Flipped: ${d.flip}, Index: ${d.index}`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more