Consider the following tabular data (just an example):
A,B,C,D,x,y,z
a0,b0,c0,d0,0.007,0.710,0.990
a0,b0,c0,d1,0.283,0.040,1.027
a0,b0,c1,d0,0.017,0.688,2.840
a0,b0,c1,d1,0.167,0.132,2.471
a0,b1,c0,d0,0.041,0.851,1.078
a0,b1,c0,d1,0.235,1.027,1.027
a0,b1,c1,d0,0.037,0.934,2.282
a0,b1,c1,d1,0.023,1.049,2.826
a1,b0,c0,d0,0.912,0.425,1.055
a1,b0,c0,d1,0.329,0.932,0.836
a1,b0,c1,d0,0.481,0.681,0.997
a1,b0,c1,d1,0.782,0.595,2.294
a1,b1,c0,d0,0.264,0.918,0.857
a1,b1,c0,d1,0.053,1.001,0.920
a1,b1,c1,d0,1.161,1.090,1.470
a1,b1,c1,d1,0.130,0.992,2.121
Note that each combination of distinct values for columns A
, B
, C
, and D
occurs exactly once in this table. Hence, one can think of this subset of columns as the "key columns", and the remaining columns as the "value columns".
Let's say this data is in some file data.csv
, and that we read this file in with d3.csv
, into the callback argument data
, like so:
d3.csv('data.csv', function (error, data) {
...
});
I'm looking for a convenient d3.js
manipulation to transform data
so that the C
column is "pivoted". By this I mean that the "value" columns of the transformed table are those obtained by "crossing" the values of the C
column with the original "value" columns, x
, y
, and z
. In other words, in csv format, the transformed table would look like this:
A,B,D,x_c0,x_c1,y_c0,y_c1,z_c0,z_c1
a0,b0,d0,0.007,0.017,0.710,0.688,0.990,2.840
a0,b0,d1,0.283,0.167,0.040,0.132,1.027,2.471
a0,b1,d0,0.041,0.037,0.851,0.934,1.078,2.282
a0,b1,d1,0.235,0.023,1.027,1.049,1.027,2.826
a1,b0,d0,0.912,0.481,0.425,0.681,1.055,0.997
a1,b0,d1,0.329,0.782,0.932,0.595,0.836,2.294
a1,b1,d0,0.264,1.161,0.918,1.090,0.857,1.470
a1,b1,d1,0.053,0.130,1.001,0.992,0.920,2.121
In case there's no easy way to do this, a simpler (but still useful) variant would be to do a similar transformation after first discarding all but one of the "value" columns. For example, after discarding the x
and y
columns, pivoting the C
column would produce (in csv format):
A,B,D,c0,c1
a0,b0,d0,0.990,2.840
a0,b0,d1,1.027,2.471
a0,b1,d0,1.078,2.282
a0,b1,d1,1.027,2.826
a1,b0,d0,1.055,0.997
a1,b0,d1,0.836,2.294
a1,b1,d0,0.857,1.470
a1,b1,d1,0.920,2.121
The simplification lies in that now the original value column (z
) can be simply replaced by a set of columns named after the values (c0
and c1
in this case) in the column that was pivoted (C
).
You are looking for d3.nest
:
d3.csv('data.csv', function (data) {
var nester = d3.nest()
.key(function (d) { return d.A; })
.key(function (d) { return d.B; })
.key(function (d) { return d.D; })
.rollup(function (values) {
var sortedValues = values.sort(function (x, y) {
return x.C < y.C ? -1 : x.C > y.C ? 1 : 0;
});
var mkKey = function (c, name, v) {
return {
name: 'C_' + c + '_' + name,
value: v
};
}
var pivotedX = sortedValues.map(function (d) { return mkKey(d.C, 'x', d.x); }),
pivotedY = sortedValues.map(function (d) { return mkKey(d.C, 'y', d.y); }),
pivotedZ = sortedValues.map(function (d) { return mkKey(d.C, 'z', d.z); });
return Array.prototype.concat.apply([], [pivotedX, pivotedY, pivotedZ]);
});
var nestedData = nester.entries(data);
var pivotedData = [];
nestedData.forEach(function (kv1) {
var a = kv1.key;
kv1.values.forEach(function (kv2) {
var b = kv2.key;
kv2.values.forEach(function (kv3) {
var d = kv3.key;
var obj = {
A: a,
B: b,
D: d
};
kv3.values.forEach(function (d){
obj[d.name] = d.value;
})
pivotedData.push(obj);
})
})
})
console.log(JSON.stringify(pivotedData, null, ' '));
});
The result in nestedData
would be of the following form:
[
{
"A": "a0",
"B": "b0",
"D": "d0",
"C_c0_x": "0.007",
"C_c1_x": "0.017",
"C_c0_y": "0.710",
"C_c1_y": "0.688",
"C_c0_z": "0.990",
"C_c1_z": "2.840"
},
...,
{
"A": "a1",
"B": "b1",
"D": "d1",
"C_c0_x": "0.053",
"C_c1_x": "0.130",
"C_c0_y": "1.001",
"C_c1_y": "0.992",
"C_c0_z": "0.920",
"C_c1_z": "2.121"
}
]
Demo Look at script.js
and the output on the console
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With