Cuando se desea observar rápidamente la distribución de una secuencia de valores, el diagrama de tallo y hojas es una herramienta increíblemente simple pero poderosa. Se necesitan unos minutos para enseñarle a la computadora a dibujarlo, o puedes hacerlo trivialmente a mano.
El único problema es que no conserva el orden de los valores, que en ocasiones contiene información útil. He estado tratando de pensar en una forma igualmente sencilla de trazar una serie temporal con orden conservado, pero no se me ocurre nada.
La solución obvia de hacer un gráfico de serie temporal regular con el tiempo en el eje X y los valores en el eje Y adolece del problema de que necesita bastante trabajo preparatorio antes de pasar a la renderización real. No es tan simple conceptualmente como el diagrama de tallo y hojas.
¿Hay algo que sea? ¿O es imposible lo que pido?
Ah, y un requisito importante que casi olvido: me gustaría que esto se pudiera imprimir fácilmente en una terminal con buffer de línea...
La razón por la que pregunto aquí es porque mi principal caso de uso para esto son las métricas de estado y otras muestras de los servidores. Al descartar las causas de un sistema que funciona mal, sería bueno intuir rápidamente cómo se ha comportado algún subsistema a lo largo del tiempo.
Respuesta1
No estoy seguro de que esto sea exactamente lo que estoy buscando, ya que es un poco complejo, pero hasta ahora ha satisfecho mis necesidades:
Comencé a trabajar con temperaturas, todas las cuales estaban convenientemente en el rango de 25 a 60 °C, por lo que simplemente podía replicar una cadena *
para crear una especie de diagrama de barras de ellas:
$ cat temps2.txt | perl -pe 'print "*" x $_ . " ";'
******************************************** 44.0
*************************************************** 51.0
******************************************* 43.0
********************************************* 45.0
************************************** 38.0
**************************************** 40.0
*********************************** 35.0
************************************ 36.0
******************************** 32.0
******************************** 32.0
******************************* 31.0
******************************* 31.0
******************************** 32.0
******************************** 32.0
******************************* 31.0
************************************ 36.0
******************************** 32.0
************************************ 36.0
******************************* 31.0
*********************************** 35.0
************************************ 36.0
************************************ 36.0
********************************* 33.0
******************************* 31.0
******************************** 32.0
******************************* 31.0
********************************* 33.0
******************************** 32.0
******************************** 32.0
************************************ 36.0
Por supuesto, esto sólo funciona con valores que están en un rango conveniente, pero es estúpidamente efectivo cuando lo están – y cuando no lo están, uno puede simplemente agregar algunas manipulaciones aritméticas a la $_
variable que indica el número de repeticiones.
Por ejemplo, la longitud promedio de la cola de ejecución del procesador cada segundo (que para mí está en el rango de 0 a 8) se puede multiplicar por 10 para que los cambios sean visibles en la salida:
$ cat runq.txt | perl -pe 'print "*" x ($_ * 10) . " ";'
0
0
0
0
******************** 2
********** 1
******************** 2
**************************************** 4
****************************** 3
**************************************** 4
****************************** 3
****************************** 3
******************** 2
******************** 2
********** 1
********** 1
********** 1
************************************************************ 6
********** 1
********** 1
********** 1
0
0
Esto habría satisfecho absolutamente mis necesidades.
Por supuesto, siendo yo, tomé este camino por la borda y creé un gran script que incluye el cálculo y la actualización automáticos de las transformaciones de coordenadas, y también el cálculo en tiempo real del promedio del sistema y los límites naturales del proceso:
$ cat temps2.txt | ./limits.pl
----------------------------------------------------------------
X: 51.0 [ | * ]
X: 43.0 [ * | ]
X: 45.0 [ | ]
X: 38.0 [ * | ]
X: 40.0 [ * | ]
X: 35.0 [ * | ]
X: 36.0 [ * | ]
X: 32.0 [ * | ]
X: 32.0 [ * | ]
X: 31.0 [* | ]
X: 31.0 [* | ]
X: 32.0 [ * | ]
X: 32.0 [ * | ]
X: 31.0 [* | ]
X: 36.0 [ * ]
X: 32.0 [ * | ]
X: 36.0 [ | ]
X: 31.0 [ * | ]
X: 35.0 [ *| ]
X: 36.0 [ | ]
X: 36.0 [ * ]
X: 33.0 [ * | ]
X: 31.0 [ * | ]
X: 32.0 [ * | ]
X: 31.0 [ * | ]
X: 33.0 [ *| ]
X: 32.0 [ * | ]
X: 32.0 [ * | ]
X: 36.0 [ |* ]
UPL=42.1
Xbar=35.2
LPL=28.2
También se adjunta código fuente sucio a este script. Este es el primer borrador, así que disculpe el código incorrecto.
#!/usr/bin/env perl
use v5.26;
use strict;
use warnings;
use List::Util qw( min max );
my $max_width = 52;
my $n = 0;
my $xbar = 0;
my $mrbar = 0;
my $lpl;
my $upl;
sub print_values {
print "\n";
printf "UPL=%.1f\n", $upl;
printf "Xbar=%.1f\n", $xbar;
printf "LPL=%.1f\n", $lpl;
}
$SIG{INT} = \&print_values;
my $min_y;
my $max_y;
my $xprev;
while (my $x = <>) {
$n++;
$xbar *= $n - 1;
$xbar += $x;
$xbar /= $n;
if (defined($xprev)) {
my $mr = abs ($x - $xprev);
$mrbar *= $n - 2;
$mrbar += $mr;
$mrbar /= $n - 1;
$lpl = $xbar - $mrbar * 2.66;
$upl = $xbar + $mrbar * 2.66;
my $space_changed;
# If any point is about to be drawn outside of the screen space, expand
# the space to include the currently drawn points and then some.
if (min($lpl, $x) < $min_y or max($upl, $x) > $max_y) {
my $min_diff = abs($min_y - min($lpl, $x));
my $max_diff = abs($max_y - max($upl, $x));
# Change min and max values in slightly larger steps to avoid
# changing the space too often with a drifting process.
$min_y -= $min_diff * 2;
$max_y += $max_diff * 2;
$space_changed = 1;
}
if ($min_y == $max_y) {
$max_y = $min_y + 1;
}
my %screen_coords;
$screen_coords{lpl} = $lpl;
$screen_coords{upl} = $upl;
$screen_coords{xbar} = $xbar;
$screen_coords{x} = $x;
# Transform the recorded values to the screen space.
for my $coord (keys %screen_coords) {
# Set offset to 0.
$screen_coords{$coord} -= $min_y;
# Divide by range to scale down to 0–1.
$screen_coords{$coord} /= ($max_y - $min_y);
# Scale up again to proper width.
$screen_coords{$coord} *= ($max_width - 1);
}
# Render the recorded values into an array of characters.
my @characters = split('', ' ' x $max_width);
$characters[$screen_coords{xbar}] = '|';
$characters[$screen_coords{lpl}] = '[';
$characters[$screen_coords{upl}] = ']';
$characters[$screen_coords{x}] = '*';
# Print a separator whenever the space needs to be expanded.
if ($space_changed) {
printf ('-' x ($max_width + 12) . "\n");
}
printf "X: %7.1f %s\n", $x, join('', @characters);
} else {
$min_y = $x;
$max_y = $x;
}
$xprev = $x;
}
print_values;