#algorithm #math #d3.js #graph #data-science
#алгоритм #математика #d3.js #График #наука о данных
Вопрос:
Это скорее вопрос науки о данных, чем d3.js но я думаю, что другие люди, должно быть, тоже думали об этом.
У меня есть набор данных с ежедневными значениями обновления. Набор также содержит исторические данные за все или несколько дней. В основном так:
{data: [
"ItemA" : {
"24.10.2020" : 123,
"25.10.2020" : 134,
"26.10.2020" : 145,
"27.10.2020" : 156,
"28.10.2020" : 167
},
"ItemB" : {
"24.10.2020" : 123,
"25.10.2020" : 234,
"26.10.2020" : 456,
"27.10.2020" : 567,
"28.10.2020" : 678
},
"ItemC" : {
"24.10.2020" : 123,
"25.10.2020" : 136,
"26.10.2020" : 149,
"27.10.2020" : 152,
"26.10.2020" : 165,
"28.10.2020" : 178
},
]}
Как вы видите, itemB — это выброс, значения которого растут намного быстрее, чем у других элементов.
Настройка масштаба для условных обозначений для отображения роста с течением времени была простой, если значения росли почти с одинаковой скоростью. A d3.scaleLinear().domain([0, upperBoundValues])
был в порядке. В то время как значения росли, пользователь все еще мог различать меньшие и более высокие значения.
Поскольку один элемент рос быстрее, тот, у которого рост медленнее, перемещается в одну часть шкалы. Итак, если бы у меня был такой цветовой диапазон, как d3.interpolateTurbo
вдруг большинство значений отображаются в виде цветов, близких к черному, и один всегда к красному.
Вручную я бы переключился на шкалу мощности или логарифмическую шкалу. Особенно потому, что мне пришлось бы ежедневно проверять, что происходит.
Я бы предпочел иметь функцию, которая проверяет такие события и автоматически переключает масштаб, если значения. Еще лучше было бы неплохо выбрать подходящий масштаб (в основном выбирая наиболее подходящий показатель степенной шкалы и / или базы для логарифмических шкал). Мне не нужна логарифмическая шкала base10, если мои значения никогда не вырастут до сверхбольших цифр.
Есть ли какая-либо функция / алгоритм аппроксимации, которую я могу реализовать, которая упрощает выбор или возвращает значение, при котором я мог бы выбрать масштаб (например: 0 … 1 -> Линейный // 1 … n -> Log)
Комментарии:
1. Обычно я бы сказал, что вы хотите видеть значения относительно друг друга. И если это означает, что одна строка на линейной диаграмме больше, то, по крайней мере, перспектива та же. Но звучит так, как будто вы хотите знать тенденцию , чтобы вы могли видеть, какая линия растет быстрее, чем другие. В таком случае, почему бы не избежать этой проблемы и не использовать индексацию: для каждой строки первое значение равно 100%, а все последующие точки относятся к этому значению. Вы все равно можете видеть тенденцию, и вам не нужно менять масштаб, потому что он сбрасывается каждый день до 100%
2. Тенденция играет в этом роль, но имеет значение только с точки зрения алгоритма. Фактический вариант использования — это карта с несколькими областями (ItemA …), Которые должны быть окрашены в соответствии с имеющимся у них значением. [На карте есть ввод временного слайдера для временных рядов] Если у меня есть один выброс, который просто отличается от других областей, наступит момент, когда у меня будет много черных областей и одна красная (чтобы продолжить мой турбо-пример). Я бы хотел по-прежнему отличать другие области друг от друга. Также может быть интересно сопоставить это с пороговым масштабом с пороговыми значениями, которые имеют смысл…
3. Когда вы начали о карте, я подумал то же самое. Я бы устанавливал пороговые значения не регулярно, а с использованием 1-9 децилей. Таким образом, ваш пример turbo будет красным, но остальные все равно будут дифференцируемыми
4. Я использовал нечто подобное на карте Covid, которую я сделал с заболеваемостью. Мне приходилось отображать разные допустимые пороговые значения (0,20, 35, 50, верхний предел). Когда-то несколько округов прошли отметку 50, но стали больше. Я противопоставил этому новые пороговые значения для 100, 250 и верхней границы. Но заболеваемость covid будет расти и падать математически более предсказуемым образом.
5. С этим другим масштабом я не могу делать никаких прогнозов. Единственное, что я могу знать, это то, что будет несколько, которые могут иметь высокие значения, но большинство значений похожи друг на друга.
d3.scaleQuantile()
с 1-9-м децилями выглядит многообещающе, но, вероятно, потерпит неудачу, если один из выбросов выберет логарифмический, я думаю. Я попробую квантировать и вернусь к этому. — Есть ли способ получить «хорошие» шаги для них? (Например, «100» предпочтительнее «99» в качестве порогового шага)
Ответ №1:
В качестве продолжения моего комментария рассмотрим следующее, в котором используются scaleThreshold
децильные значения. Я нарисовал 5 кругов, и значение первого увеличивается намного быстрее, чем у остальных. Но вы все равно увидите достаточную разницу между ними из-за порогового масштаба.
const data = d3.range(5).map(i => {
let values = [1];
d3.range(50).forEach(() => {
// Either a multiplier [0.9, 1.2], or (if it's the first one, [1.2, 1.5]
const multiplier = (i === 0 ? 1.2 : 0.9) (Math.random() * 0.3);
values.push(values[values.length - 1] * multiplier);
});
return {
x: 50 i * 100,
y: 50,
r: 40,
values: values,
};
});
const allValues = data.map(d => d.values).flat().sort((a, b) => a - b);
const colours = d3.scaleThreshold()
.domain([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9].map(i => d3.quantile(allValues, i)))
.range(d3.schemeSpectral[10]);
const svg = d3.select("svg")
.attr("width", 500);
const colourbar = svg.append("g");
colourbar
.selectAll("rect")
.data(colours.range())
.enter()
.append("rect")
.attr("x", (d, i) => i * 50)
.attr("y", 100)
.attr("height", 20)
.attr("width", 50)
.attr("fill", d => d);
colourbar
.selectAll("text")
.data(colours.domain())
.enter()
.append("text")
.attr("x", (d, i) => (i 1) * 50)
.attr("y", 135)
.text(d => d.toFixed(1));
const circles = svg.append("g")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.r);
const labels = svg
.append("g")
.selectAll("text")
.data(data)
.enter()
.append("text")
.style("fill", "white")
.attr("dy", 5)
.attr("x", d => d.x)
.attr("y", d => d.y);
let counter = -1;
function colour() {
counter = (counter 1) % 50;
labels.text(d => d.values[counter].toFixed(1));
circles
.transition()
.duration(1000)
.ease(d3.easeLinear)
.attr("fill", d => colours(d.values[counter]))
.filter((d, i) => i === 0)
.on("end", colour);
}
colour();
text {
text-anchor: middle;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
Комментарии:
1. Вау, очень аккуратно. Я попробую это и дам вам знать, насколько хорошо это работает для моего варианта использования. Даже если это не так, я думаю, это может оказаться очень полезным.
2. Я добавил цветную полосу к примеру, я думаю, это может помочь лучше проиллюстрировать его