我正在尝试在 javascript 中重现 d3 密度岭图,但使用不同的数据集。我正在修改的代码来自 d3 库的文档,可以在这里看到:d3 密度脊示例。
我看了一下这个例子中的数据。它是一个对象数组:
d3.csv("https://raw.githubusercontent.com/zonination/perceptions/master/probly.csv",
function(data) {console.log(data)}
)
[
{
"Almost Certainly": "95",
"Highly Likely": "80",
"Very Good Chance": "85",
"Probable": "75",
"Likely": "66",
"Probably": "75",
"We Believe": "66",
"Better Than Even": "55",
"About Even": "50",
"We Doubt": "40",
"Improbable": "20",
"Unlikely": "30",
"Probably Not": "15",
"Little Chance": "20",
"Almost No Chance": "5",
"Highly Unlikely": "25",
"Chances Are Slight": "25"
}
...
]
所以我试图用我自己的数据集(一个著名的钻石数据集)替换原始数据集。为了重现我的问题,这里有一些代码,用于下载钻石数据集并将其转换为与上面数据集相同的形式。
let diamondsDataRaw;
d3.csv("https://raw.githubusercontent.com/tidyverse/ggplot2/main/data-raw/diamonds.csv", function(data){
diamondsDataRaw_ = data
}
)
// Get unique cuts
var cuts = [...new Set(diamondsDataRaw.map(d => d.cut))];
// map each distinct cut to cut & price
var distinctCutPrice = cuts.map(cut => {
var cut_data = diamondsDataRaw.filter(d => d.cut === cut)
var distinctCutPrice = cut_data.map(i => {return({cut:i.cut, price:i.price})})
return(distinctCutPrice)
})
// make all same length
var minLen = ss.min(distinctCutPrice.map(i => i.length))
distinctCutPrice_same_length = distinctCutPrice.map(i => i.slice(0, minLen))
// convert to dataframe
var seqMinLen = [...Array(minLen)].map((v, k)=>k)
var df = seqMinLen.map(i => {
return({
Ideal: distinctCutPrice_same_length[0][i].price,
Premium: distinctCutPrice_same_length[1][i].price,
Good: distinctCutPrice_same_length[2][i].price,
"Very Good": distinctCutPrice_same_length[3][i].price,
Fair: distinctCutPrice_same_length[4][i].price
})
})
现在我尝试使用他们的代码,但交换我的数据集:
<div id = "plot33"></div>
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(function(x) {
return [x, d3.mean(V, function(v) { return kernel(x - v); })];
});
};
}
function kernelEpanechnikov(k) {
return function(v) {
return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0;
};
}
var data = df
// Get the different categories and count them
var categories = cuts
var n = categories.length
// set the dimensions and margins of the graph
var margin = {top: 60, right: 30, bottom: 20, left:110},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#plot33")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")"
);
// Add X axis
var x = d3.scaleLinear()
.domain([-10, 140])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Create a Y scale for densities
var y = d3.scaleLinear()
.domain([0, 0.4])
.range([ height, 0]);
// Create the Y axis for names
var yName = d3.scaleBand()
.domain(categories)
.range([0, height])
.paddingInner(1)
svg.append("g")
.call(d3.axisLeft(yName));
// Compute kernel density estimation for each column:
var kde = kernelDensityEstimator(kernelEpanechnikov(7), x.ticks(40)) // increase this 40 for more accurate density.
var allDensity = []
for (i = 0; i < n; i++) {
var key = categories[i];
var density = kde(data.map(function(d) { return parseFloat(d[key]); }));
allDensity.push({key: key, density: density});
}
// Add areas
svg.selectAll("areas")
.data(allDensity)
.enter()
.append("path")
.attr("transform", function(d){return("translate(0," + (yName(d.key)-height) +")" )})
.datum(function(d){return(d.density)})
.attr("fill", "#69b3a2")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); })
)
如有任何帮助,我们将不胜感激。我的最终目标是尝试制作这样的东西:
代码中的一些问题:
1 - 您正在尝试在
diamondsDataRaw
中的 async 回调之外使用 d3.csv
。
2 - 我没有遵循您的数据操作,并且无法真正通过
ss.min
部分(它是未定义的)。所以我重写了它更简单(但我不确定这就是你想要的)。
3 - 您下面的示例在创建其比例时使用了一些硬编码的幻数。这些不适用于您的不同数据集。
将这些放在一起,这是一个可以工作的代码示例。它肯定需要进一步完善,因为作为数据可视化它没有什么意义。从数据来看,降价只是价格的驱动因素之一。
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
<script>
var margin = {top: 60, right: 30, bottom: 20, left:110},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
d3.csv("https://raw.githubusercontent.com/tidyverse/ggplot2/main/data-raw/diamonds.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, d3.max(data.map(d => +d.price))])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var categories = [...new Set(data.map(d => d.cut))];
var spacing = height / categories.length;
var kde = kernelDensityEstimator(kernelEpanechnikov(7), x.ticks(40))
var allDensity = [];
var yMax = 0;
categories.forEach( c => {
let density = kde(
data.filter(d => d.cut == c).map(d => +d.price)
);
let m = d3.max(density.map( d => d[1]) );
if (m > yMax) yMax = m;
allDensity.push({key: c, density: density});
});
var y = d3.scaleLinear()
.domain([0, yMax])
.range([spacing, 0]);
var yName = d3.scaleBand()
.domain(categories)
.range([0, height])
.paddingInner(1)
svg.append("g")
.call(d3.axisLeft(yName));
svg.selectAll("areas")
.data(allDensity)
.enter()
.append("path")
.attr("transform", d => "translate(0," + (yName(d.key) - spacing) +")" )
.attr("fill", "#69b3a2")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.attr("d", d => {
let p = d3.line()
.curve(d3.curveBasis)
.x(d => x(d[0]))
.y(d => y(d[1]))(d.density);
return p;
});
});
// This is what I need to compute kernel density estimation
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(function(x) {
return [x, d3.mean(V, function(v) { return kernel(x - v); })];
});
};
}
function kernelEpanechnikov(k) {
return function(v) {
return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0;
};
}
</script>