Data Sci aventuras - parte 3, un histograma de pescado

Publicado en 2021-05-27 18:18

Idiomas disponibles:

Este artículo es parte 3 de serie de los artículos (dos primeros están solo en inglés). En esos artículos exploré conjunto de datos de pescado con Python y R. En este artículo está un código de histograma.

Lo siento para errores gramaticales, español es mi quinto idioma.

Desafortunadamente en el libro de Scott Murray no está un ejemplo de histograma. Tuve que mirar ObservableHQ para un ejamplo de función d3.bin. Los ejemplos allí pueden estar confudo. Este artículo es mis notas.

Mi primero paso estaba filtrando los datos de la especie en Python y archivando en JSON.

for i in range(len(group)):
    partial = {}
    for column in list(group.columns):
        partial[column] = group.iloc[i][column]
    result.append(partial)

with open(os.path.join(os.getcwd(), 'grouped', f"{specie}.json"), 'w') as f:
    f.write(json.dumps(result))

El secundo paso estaba trayendo los datos en JavaScript:

d3.json("url to data", (data) => {
        return data
    }).then((data) => {
      // Code will be here  
    })

La parte más importante está haciendo los datos para el histograma. 'D3.js` tiene una función bin() cuál entrega otra función. La segunda función calcula las botes.

const buckets = d3.bin()(data.map((item) => item["Weight"]));

El próximo paso está la configuración para el gráfico:

const width = 350,
      height = 300,
      margin = { top: 60, right: 20, bottom: 40, left: 40},
      maxBins = d3.max(buckets, d => d.length),
      max = buckets[buckets.length - 1].x1,
      min = buckets[0].x0,
      svg = d3.select("#hist")
              .append("svg")
              .attr("height", height)
              .attr("width", width),

El gráfico necesita los ejes:

x = d3.scaleLinear()
       .domain([min, max])
       .range([30, width - 30])
       .clamp(false),
y = d3.scaleLinear()
      .domain([0, maxBins])
      .nice() // Returns a new interval [niceStart, niceStop] covering the given interval [start, stop] and where niceStart and niceStop are guaranteed to align with the corresponding tick step.
      .range([height - margin.bottom, margin.top]), // Returns an array containing an arithmetic progression, similar to the Python built-in range.
xAxis = g => g.attr("transform", `translate(0,${height - margin.bottom})`)
              .call(d3.axisBottom(x).tickSizeOuter(0))
              .call(g => g.append("text")
                          .attr("x", (width - margin.right)/2)
                          .attr("y", 35)
                          .attr("fill", "#000")
                          .attr("text-anchor", "middle")
                          .text("Weight [g]")
                    );

Ahora todos están preparado. En primero parte código adjunta el g elemento a svg cuál contiene los rectángulos de histograma. La función más importante está data() que pasa por los datos.

La segunda parte adjunta el x-eje.

svg.append("g")
   .selectAll("rect")
   .data(buckets)
   .join("rect")
   .attr("fill", (d => binColor(d.x0)))
   .attr("x", d => x(d.x0) + 1)
   .attr("width", d => Math.max(0, x(d.x1) - x(d.x0) - 1))
   .attr("y", d => y(d.length))
   .attr("height", d => y(0) - y(d.length));

svg.append("g").call(xAxis);

No bueno gráfico es complete sin los rótulos y el título.

const labels = svg.append("g")
                  .selectAll("text")
                  .data(buckets.filter(d => d.length > 0))
                  .join("text")
                  .attr("x", d => ((x(d.x0) + x(d.x1)) / 2) | 0)
                  .attr("y", d => y(d.length) - 2)
                  .style("fill", "black")
                  .style("font-size", 10)
                  .style("text-anchor", "middle");
      labels.text(d => {
            if (x(d.x1) - x(d.x0) < 50) {
                return d.length
            } else if (d.length > 1) {
                return `${d.length} items`
            } else if (d.length === 1) {
                return "1 item"
            } else {
                return "empty bucket"
            }
        });

svg.append("g")
   .append("text")
   .text("Bream weight distribution")
   .style("fill", "#000")
   .attr("font-weight", "bold")
   .style("font-size", 14)
   .style("text-anchor", "end")
   .attr("x", 250)
   .attr("y", 30);

Todos juntos:

Enlace a los datos de pescado