Notxor tiene un blog

Defenestrando la vida

Tablas para cálculos en org-mode

Notxor
2021-01-22

No es la primera vez que en este blog hablo sobre las tablas y todas las cosas que nos proporcionan a los usuarios de org-mode. Algunos, al haberme referido a ellas como hojas de cálculo, esperaban tener la suerte de que se utilizaran de la misma manera y se han topado con un pequeño muro de dificultades. Me han preguntado por cómo funcionan y cómo las pueden utilizar y por qué las he llamado en alguna ocasión hojas de cálculo en texto plano si no lo son. Este artículo trata esos temas e intentará dar ejemplos suficientes para aquellos que habiendo manejado hojas de cálculo, quieran probar la potencia de las tablas de org-mode.

Direcciones de celda

Si tienes experiencia con las hojas de cálculo, desde que Lotus 1-2-3 impuso la nomenclatura de utilizar letras para las columnas y números para las filas, la cosa ha cambiado poco. El primer concepto a tener en cuenta, en org-mode, es que en las tablas no sólo hay direcciones absolutas, sino también relativas: por ejemplo, podemos utilizar la versión absoluta de una celda con el formato @FILA$COLUMNA, además, las columnas, por ejemplo, ─pero las filas igual─, se pueden referenciar como $+1 o $-2... o también $< y $> son referencias a la primera y la última de las columnas. De manera similar ocurre con las filas: se pueden utilizar las referencias @-2 ó @+3, de forma relativa o @< y @> para la primera y la última fila respectivamente. Teniendo en cuenta que las líneas de separación no se cuentan ni en vertical | ni en horizontal -, sólo cuentan las filas y columnas con datos. @0 implica la fila actual y $0 la columna actual, aunque generalmente se omiten. De esta manera si el cálculo de la celda @2$3 implica el contenido de otra celda de la fila, pongamos la de la columna 5, podríamos escribir la referencia como @2$5, o @0$5, o $5. Si utilizamos la primera, la dirección es absoluta. Si luego queremos aplicar la fórmula en otras celdas de otras filas, el cálculo se hará siempre con el contenido de esa celda particular. Las otras dos formas son equivalentes y referencian el contenido de la misma fila en su quinta columna.

La forma de los rangos de celdas es similar a las hojas de cálculo a las que estamos acostumbrados, se utiliza la notación de dos caracteres .: $1..$4 indica las columnas de la uno a la cuatro de la fila actual.

También se pueden referenciar valores que se encuentran en otras tablas. Para no liarnos mucho, vamos con un ejemplo sencillo, aunque más práctico.

Ejemplo práctico

Vamos a suponer que queremos montar nuestro propio sistema de calificaciones, de una asignatura. Tenemos cinco alumnos y hemos planificado tres exámenes distintos, de tipo test. En realidad lo podemos hacer con una tabla un poco más compleja, como veremos al final, pero para que sirva de ejemplo, haremos una tabla por cada examen parcial.

Vamos con la práctica, copia la siguiente tabla o escribe una similar, primero vamos con la definición de los exámenes:

#+name: Pruebas
| Examen | Preguntas | Alternativas |
|--------+-----------+--------------|
|        |           |              |
|        |           |              |
|        |           |              |

En esta tabla no vamos a hacer cálculos, sino definir las características de nuestros exámenes. El ponerle un nombre con el parámetro #+name: no aporta nada visualmente, pero nos permitirá referenciar el contenido de la misma con la forma remote(name,ref). Vamos a rellenar los campos con los siguientes datos.

Examen Preguntas Alternativas
P1 40 4
P2 35 5
P3 100 2

Y ahora vamos a preparar nuestra primera tabla, para el primer examen.

#+name: NotasP1
| N | Nombre          | Aciertos | Errores | Nota | Dec |
|---+-----------------+----------+---------+------+-----|
| 1 | Pepe López      |          |         |      |     |
| 2 | Amanda Torres   |          |         |      |     |
| 3 | Juan Pérez      |          |         |      |     |
| 4 | Elisa Ronchón   |          |         |      |     |
| 5 | Adalberto Épila |          |         |      |     |

Una vez creada la plantilla sitúo el cursor en la celda de la primera línea de datos, que corresponde al ficticio alumno Pepe López justo en la bajo la columna de Nota.

Captura-pantalla_nueva-formula.png

Vamos a meter ahora la fórmula de calificación de un examen de alternativas. Como sabéis, el cálculo se no sólo a los aciertos obtenidos, sino que se elimina el factor de puntuación obtenida por respuestas aleatorias que pueden dar los alumnos, restando a esos aciertos el número de errores dividido por el número de alternativas menos uno. Es decir, siendo los aciertos \(A\), los errores \(E\) y las alternativas \(a\) la fórmula sería:

\[ Nota = A - \frac{E}{a - 1} \]

Para introducir dicha fórmula, situados en la celda que he mencionado antes, tecleamos a continuación la fórmula:

:=$3-($4/(remote(Pruebas,@2$3)-1))

Como se ve, primero se teclea la entrada := y a continuación las referencias de las celdas que necesitamos. $3 será el número de aciertos en la prueba de nuestro ficticio alumno Pepe López, $4$ es la celda en la columna de /Errores/ para la misma fila y luego, necesitamos las alternativas, que está en la tabla que hemos llamado ~Pruebas y lo llamamos con remote(Pruebas,@2$3). Al pulsar <RET>, o <TAB> obtenemos algo así

Captura-pantalla_primera-formula.png

Hay que fijarse especialmente en la última línea que ha aparecido automágicamente: es nuestra fórmula.

#+TBLFM: @2$5=$3-($4/(remote(Pruebas,@2$3)-1))

Vamos a deconstruirla para entenderla. Tiene una etiqueta especial con la forma #+tblfm:, que viene a significar algo así como fórmulas de la tabla. A continuación establece que la celda (@2$5) se calcula con nuestra fórmula asignando a la referencia su valor con un signo =.

Puesto que queremos que se pueda calcular con la misma fórmula todas las celdas de la columna, podemos borrar de esa referencia el @2. Así pues, sitúa el cursor en la línea de la fórmula y borra el primer @2 de la izquierda, la línea debe quedar así:

#+TBLFM: $5=$3-($4/(remote(Pruebas,@2$3)-1))

Sin mover el cursor de esa línea pulsa C-c C-c y la tabla se recalcula así:

Captura-pantalla_recalcular-formula.png

Muy bonito, pero aún no calcula nada, porque no hay datos. Vamos a repetir el proceso de cálculo para que lo veamos más claro. Introduce los siguientes datos en ella:

  1. Pepe López: Aciertos = 35, Errores = 5
  2. Amanda Torres: Aciertos = 35, Errores = 6
  3. Juan Pérez: Acierto*s = 32, *Errores 3
  4. Elisa Ronchón: Aciertos = 40, Errores = 0
  5. Adalberto Épila: Aciertos = 36, Errores = 4

Regresa a la línea de la fórmula tras haber introducido esos datos y pulsa C-c C-c.

Captura-pantalla_calculo-con-datos.png

Está calculado... pero queda algo fea la tabla con los números desalineados, unos con 5 decimales y otros sin ningunos. Para solucionarlo, situados en la línea de la fórmula, la completamos añadiendo a la misma ;%.3f. Dicho de otro modo: separamos la fórmula del formato con el caracter ; y le decimos que el formato será el valor de la celda % con un número de coma flotante de tres decimales: .3f... el resultado queda así:

Captura-pantalla_formato-celdas.png

Vamos con nuestra última columna. Colocándonos en cualquier celda de la misma tecleamos la siguiente secuencia:

:=10*($5/remote(Pruebas,@2$2));%.3f

Como vemos, es sólo una fórmula sencilla que convierte la nota directa en nota decimalizada y también le da un formato de tres decimales.

Las fórmulas de las dos columnas se encuentran en la misma cláusula #+tblfm, sin embargo también podríamos tener las fórmulas separadas de la siguiente manera:

#+name: NotasP1
| N | Nombre          | Aciertos | Errores |   Nota |    Dec |
|---+-----------------+----------+---------+--------+--------|
| 1 | Pepe López      |       35 |       5 | 33.333 |  8.333 |
| 2 | Amanda Torres   |       35 |       6 | 33.000 |  8.250 |
| 3 | Juan Pérez      |       32 |       3 | 31.000 |  7.750 |
| 4 | Elisa Ronchón   |       40 |       0 | 40.000 | 10.000 |
| 5 | Adalberto Épila |       36 |       4 | 34.667 |  8.667 |
#+tblfm: $5=$3-($4/(remote(Pruebas,@2$3)-1));%.3f
#+tblfm: $6=10*($5/remote(Pruebas,@2$2));%.3f

Dividirlo así puede facilitar la lectura y la edición de las diversas fórmulas de una tabla. Podemos, además, calcular las fórmulas que hayamos modificado exclusivamente o quizá recalcular toda la tabla. Para hacer que se recalculen todas las fórmulas se utiliza la combinación de teclas C-c *. Si tuviéramos que calcular algunas fórmulas que dependan unas de otras, pueden necesitar que se calculen de forma recursiva, si fuera necesario ésto se hace con C-u C-u C-c *.

De todas formas, encuentro que es más interesante tener las fórmulas en una sola línea y editarlas con la combinación C-c '. Este comando abrirá un buffer de edición donde se listan las fórmulas de la tabla, una por línea, para trabajar más cómodamente.

Las fórmulas pueden utilizar cualquier función que comprenda el paquete calc. Pero además, también podemos utilizar la notación de elisp, Por ejemplo, podemos escribir '(apply '+ '($1..$4));N'

Captura-pantalla_notas-3-parciales.png

Tablas complejas

Hasta ahora en el artículo se ha seguido una política de tablas simples: cálculos directos de filas y columnas sin marear mucho la perdiz, aunque se han utilizado celdas de otras tablas. Sin embargo, también se pueden utilizar algunas características de tablas más complejas, como poner nombre a columnas o parámetros.

Para poner un ejemplo, vamos a hacer todos los cálculos que realizábamos antes con cuatro tablas, ahora en una sola tabla como ésta:

|   | Nombre          |  D_1 |  E_1 |  D_2 |  E_2 |  D_3 |  E_3 | 1^er Par. | 2^o Par. | 3^er Par. | Final |
|---+-----------------+------+------+------+------+------+------+-----------+----------+-----------+-------|
| / |                 |    < |      |      |      |      |      |         < |          |         > |       |
| ! |                 |   D1 |   E1 |   D2 |   E2 |   D3 |   E3 |        N1 |       N2 |        N3 |       |
|---+-----------------+------+------+------+------+------+------+-----------+----------+-----------+-------|
| # | Pepe López      |   35 |    5 |   25 |    2 |   60 |    2 |      8.33 |     7.00 |      5.80 |  7.04 |
| # | Amanda Torres   |   35 |    6 |   18 |    3 |   55 |    3 |      8.25 |     4.93 |      5.20 |  6.13 |
| # | Juan Pérez      |   32 |    3 |   32 |    2 |   80 |   10 |      7.75 |     9.00 |      7.00 |  7.92 |
| # | Elisa Ronchón   |   40 |    0 |   25 |    0 |   55 |   10 |     10.00 |     7.14 |      4.50 |  7.21 |
| # | Adalberto Épila |   36 |    4 |   30 |    4 |   80 |    4 |      8.67 |     8.29 |      7.60 |  8.19 |
|---+-----------------+------+------+------+------+------+------+-----------+----------+-----------+-------|
| # | Medias          |      |      |      |      |      |      |      8.60 |     7.27 |      6.02 |  7.30 |
|   | Preg./Altern.   |   40 |    4 |   35 |    5 |  100 |    2 |           |          |           |       |
| ^ |                 | max1 | alt1 | max2 | alt2 | max3 | alt3 |           |          |           |       |
#+tblfm: $9=10*($D1-($E1/($alt1-1)))/$max1;%.2f::$10=10*($D2-($E2/($alt2-1)))/$max2;%.2f::$11=10*($D3-($E3/($alt3-1)))/$max3;%.2f::$12=vsum($N1..$N3)/3;%.2f::@8$9=vsum(@3..@7)/5;%.2f::@8$10=vsum(@3..@7)/5;%.2f::@8$11=vsum(@3..@7)/5;%.2f::@8$12=vsum(@3..@7)/5;%.2f

Hay que remarcar la primera columna rellena con caracteres que marcan líneas especiales. Al exportar, las líneas especiales no se imprimirán, como se puede ver a continuación:

Nombre D_1 E_1 D_2 E_2 D_3 E_3 1^er Par. 2^o Par. 3^er Par. Final
Pepe López 35 5 25 2 60 2 8.33 7.00 5.80 7.04
Amanda Torres 35 6 18 3 55 3 8.25 4.93 5.20 6.13
Juan Pérez 32 3 32 2 80 10 7.75 9.00 7.00 7.92
Elisa Ronchón 40 0 25 0 55 10 10.00 7.14 4.50 7.21
Adalberto Épila 36 4 30 4 80 4 8.67 8.29 7.60 7.59
Medias             8.60 7.27 6.02 7.18
Preg./Altern. 40 4 35 5 100 2        

A veces, en tablas complejas es difícil, a la hora de meter fórmulas saber las coordenadas de una determinada celda perdida en medio de la tabla. Para ayudarnos a esto podemos activar org-table-toggle-coordinate-overlays pulsando la combinación de teclas C-c }, no confundir con C-c { que activa ─o desactiva─ la depuración del cálculo de las fórmulas.

Captura-pantalla_edicion-formulas.png

Como se puede ver en la imagen nos muestra información sobre el número de filas y columnas, de manera que podemos editar las fórmulas más cómodamente. Para desactivarlo, volvemos a utiliza la combinación de teclas y es suficiente.

Otra cosa importante es entender las filas especiales, que no aparecen en la impresión pero que proporcionan funcionalidad interesante:

/
La fila que comienza con carácter / se refiere a la visualización de las celdas. En concreto, en este ejemplo, los caracteres < y > indican o señalan qué líneas verticales debe dibujar. Hay más opciones y es recomendable leer la documentación sobre tablas del manual de org-mode.
!
Una línea que comience con el carácter ! establecerá el nombre de la columna. Por eso en el ejemplo, en las fórmulas se emplean los nombres $D1, $E1, $D2, etc. en lugar del nombre genérico de $3, $4, etc. Eso nos permite poner nombre a las columnas que sea más fácil de interpretar para nosotros que un simple número.
#
Las filas que comienzan por un carácter # se recalcularán automáticamente cuando dentro de la tabla se puls <TAB> o <RET> o se utilice el comando C-u C-c * para recalcular la tabla. Las filas que no estén así marcadas no se ven afectadas por dicho comando.
^
Las filas que están marcadas con un carácter ^ identifican el valor de la fila superior con un nombre. En nuestras fórmulas de la tabla, se utilizan los valores $max1 o alt1, por ejemplo, en lugar de sus posiciones @10$3 o @10$4. Si en lugar de utilizar ^ encontramos - el efecto es similar, pero en ese caso, los nombres se refieren a los valores de la línea inferior.
*
Aunque en nuestro ejemplo no se ha utilizado ninguna fila marcada con un * es bueno tener en cuenta que al utilizarla hacemos que una línea sea ignorada cuando se produce un recálculo general de la tabla con el comando C-u C-c *. Esto se hace cuando los cálculos demoran mucho el redibujado de la tabla.
$
Tampoco he utilizado esta marca en la tabla. Se utiliza para establecer en una determinada fila algún valor con nombre del estilo maximo=200, para luego utilizar en las fórmulas el nombre de $maximo en lugar de una celda de la tabla.

Conclusiones

Como se puede apreciar, la forma de manejar tablas de org-mode nos pueden facilitar los cálculos para nuestra documentación. Hay muchos detalles que no he llegado a mostrar en este pequeño artículo y como siempre aconsejo: leeros la documentación que viene con org-mode, es bastante clarificadora.

En un principio pensé en incluir también la generación de gráficos con gnuplot desde nuestras tablas, pero al final lo dejo para un posterior artículo, puesto que éste ha quedado ya suficientemente pelma.

Es recomendable que practiques un poco cómo he van editando las tablas y calculando las fórmulas, para poder apreciar así, todas las facilidades que nos provee org-mode para hacerlo con más facilidad.

Categoría: org-mode emacs tablas

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en esta cuenta de Mastodon, también en esta otra cuenta de Mastodon y en Diaspora con el nick de Notxor.

Si usas habitualmente XMPP (si no, te recomiendo que lo hagas), puedes encontrar también un pequeño grupo en el siguiente enlace: notxor-tiene-un-blog@salas.suchat.org

Disculpen las molestias.