martes, 15 de marzo de 2016

Modificación de Variables Globales en BASH

[Lenguajes: BASH]

Problema

En Linux/BASH, en ocasiones es necesario modificar temporalmente alguna de las variables globales del sistema, y, a menudo esta operación se quiere hacer de manera automática empleando un programa de BASH, guardado en un archivo, que es lo que se conoce como un Script y que se invocará manualmente desde una terminal.

Para dar un poco más de contexto real al problema, supongamos que se desea cambiar una de las entradas en la variable global PATH, por otra, sin modificar ninguna otra de las entradas. Sea, por ejemplo, el siguiente el valor de la variable PATH:

# El valor de la variable PATH
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

Supongamos que en esta variable se quiere cambiar solamente la entrada /usr/games por una semejante a /disco0/usr/games, dejando intactas todas las demás entradas.

Aquí entonces el problema se subdivide en dos, a saber:

  • ¿Cómo producimos, a partir de la variable PATH, el nuevo valor que debe tener ésta?
  • ¿Cómo se establece ese resultado como el nuevo valor de la variable global PATH?

Solución

Calculando el nuevo valor que debe tener la variable PATH

Aquí proponemos dos estrategias para resolver este problema:

  1. Utilizar la substitución nativa de BASH para filtrar la variable.
  2. Emplear el editor de flujos de textos sed para filtrar la variable.

Empleo de la sustitución nativa de BASH

El formato para esta instrucción es ${ variable / que-sustituir / reemplazo }. Como tanto el que-sustituir, como el reemplazo, contienen, en este caso, la diagonal (/) que también forma parte de la instrucción, se tiene que distinguir en esos textos de entrada, justamente como texto y no como parte de la instrucción, mediante el prefijo del caracter backslash (\). De este modo, las instrucciones en BASH, para ejecutar la sustitución es como sigue:

PATH=${PATH/\/usr\/games/\/disco0\/usr\/games}
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Empleo de sed

Típicamente, sed toma como entrada un archivo, que, en caso de omisión es el stdin o standard input. Una variable común, que contenga texto, se puede convertir en el stdin, mediante el operador de indirección <<<. Por otra parte, el comando de sustitución de sed, es parecido al nativo de BASH: “s*/ que-sustituir / reemplazo /*”. La diferencia es que, en este caso, para evitar confusiones en el comando, el caracter de separación no es necesariamente la diagonal, y puede ser cualquiera que no aparezca en los textos de entrada. Así, para producir una salida como la que deseamos se podría hacer así:

sed "s:/usr/games:/disco0/usr/games:" <<<$PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Lo que hay que notar aquí, es que el resultado no se ha guardado en ninguna variable, sino que se ha mandado directamente al stdout. Para guardar el resultado en una variable se hace mediante la instrucción de expansión de comandos, $(comando ). Así que, lo que queremos lograr se hace con:

PATH=$(sed "s:/usr/games:/disco0/usr/games:" <<<$PATH)
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Guardando el valor en la variable global PATH

En lo que se ha descrito anteriormente, las instrucciones se han dado en línea, directamente en una terminal. Sin embargo, la tarea se tiene que hacer mediante un archivo script que contenga las instrucciones.

Aparte, supuestamente la instrucción export de BASH, sirve para modificar variables globales. Así que el contenido de nuestro archivo al que llamaremos "cambia.sh", será el que se muestra a continuación:

#! /bin/bash
PATH=$(sed "s:/usr/games:/disco0/usr/games:" <<<$PATH)
export PATH
echo $PATH

Intencionalmente hemos dejado la instrucción echo $PATH en el script, para monitorear
el valor de la variable en el interior del script. Se ha hecho además que el script sea ejecutable mediante la instrucción chmod +x cambia.sh, de modo que se pueda invocar su ejecución fácilmente. Veamos pues su comportamiento:

./cambia.sh
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

La primer impresión de la variable corresponde a la que se hizo en el interior del script "cambia.sh", la segunda, que como se puede ver es diferente y no manifiesta el cambio que se intentaba, corresponde al comando echo $PATH que se ha ejecutado desde la terminal. Esto indica que la variable gobal PATH no se ha cambiado.

Este resultado es producto de la forma como se ha invocado la ejecución de "cambia.sh". En este caso, se ha creado un proceso independiente, si bien no separado, del que se está ejecutando en la terminal desde donde lo hemos invocado. Para establecer su ejecución dentro del mismo proceso, se hace con la instrucción source Script, o bien . Script. La llamada queda como sigue:

source cambia.sh
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Finalmente, si queremos convertir esta llamada en un comando se puede hacer mediante la instrucción alias, que se puede poner en archivos de inicialización tales como el “.bashrc”, así:

alias cambia='source /aqui-path-completo-hacia/cambia.sh'

Problema resuelto.

miércoles, 2 de septiembre de 2015

Selección de elementos de un vector de acuerdo a un patrón

[Lenguajes: R]

Problema

Se requiere seleccionar algunos elementos de un vector de acuerdo con un patrón repetitivo. Como ejemplo, se quiere seleccionar el tercero y cuarto elementos de cada diez elementos en secuencia.

Para ilustrar el problema, supongamos que se tiene un vector que consiste en 55 numeros, empezando con el 101, como sigue:

(v <- 101:155)
##  [1] 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
## [18] 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
## [35] 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
## [52] 152 153 154 155

De ahí se quieren seleccionar, como se dijo, el tercer y cuarto elementos de cada diez elementos en la secuencia, de modo que se produzca una salida como la que se muestra a continuación:

##  [1] 103 104 113 114 123 124 133 134 143 144 153 154

Solución

En R, los vectores de lógicos se pueden utilizar para indexar otro vector, a manera de una máscara que prende o apaga los elementos del vector que se seleccionan a la salida. Así, por ejemplo, si queremos seleccionar los elementos impares en el vector v, se puede hacer como se muestra:

# Los elementos de v que no son divisibles por 2:
(ii <- v %% 2) # operación módulo
##  [1] 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
## [36] 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
# Convertimos ii a lógico
ii <- as.logical(ii)
# Y los impares son:
v[ii]
##  [1] 101 103 105 107 109 111 113 115 117 119 121 123 125 127 129 131 133
## [18] 135 137 139 141 143 145 147 149 151 153 155

Cuando el vector indexador de otro vector en R es lógico, y es de un tamaño menor al vector indexado, R lo recicla, o sea, repite la secuencia y esto es algo de lo que podemos sacar ventaja para resolver nuestro problema original; pero antes, veamos que la salida anterior, si, como es el caso, v es una secuencia de números enteros conocida, se puede producir de manera muy simple, como sigue:

v[c(TRUE, FALSE)]
##  [1] 101 103 105 107 109 111 113 115 117 119 121 123 125 127 129 131 133
## [18] 135 137 139 141 143 145 147 149 151 153 155

Si usamos este mismo comportamiento, podemos producir la máscara repetitiva del patrón, de la manera siguiente:

# Un vector lógico de 10 elementos inicializados como FALSE
ii <- logical(10)
# "Prendemos" los elementos 3 y 4 de la máscara
ii[c(3,4)] <- TRUE
# La "máscara"" es:
ii
##  [1] FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE

Ahora, simplemente se usa esa máscara como un índice del vector original v:

v[ii]
##  [1] 103 104 113 114 123 124 133 134 143 144 153 154

Y el problema ha quedado resuelto!

Notemos que no hay restricción en la clase de elementos de vector que se indexa. Hagamos, por ejemplo, un vector de 55 letras seleccionadas aleatoreamente:

(aa <- sample(letters, 55, replace=TRUE))
##  [1] "l" "s" "k" "i" "t" "f" "s" "d" "g" "d" "g" "b" "q" "w" "u" "u" "l"
## [18] "k" "v" "p" "r" "j" "h" "z" "q" "f" "d" "m" "y" "p" "z" "t" "j" "l"
## [35] "d" "a" "s" "c" "l" "q" "z" "m" "m" "e" "t" "l" "n" "f" "f" "p" "o"
## [52] "c" "a" "q" "y"

Seleccionemos de ese vector, igual, el tercero y cuarto elementos de cada diez; para ello, podemos utilizar la misma máscara generada previamente (ii):

aa[ii]
##  [1] "k" "i" "q" "w" "h" "z" "j" "l" "m" "e" "a" "q"

Si quieres ver este mismo post en mis publicaciones de Rpubs:


jueves, 30 de julio de 2015

La latosa coma decimal europea

[Lenguajes: R]

Problema

La información real proviene de fuentes heterogéneas y como tal, puede contener errores debidos a la mala definición de requerimientos a los responsables de las funtes de información, o a errores conceptuales o de configuración del software empleado en la recabación de información.

Por ejemplo, inexplicablemente, algunas versiones de Microsoft Excel traen como formatos de entrada de números, en el continente americano, la europea coma decimal, en lugar del usual y legal punto decimal. Lo peor del caso, es que, en algún levantamiento de información, sin atender a este aspecto, se conjunta toda la información levantada con diferentes formatos en una sola columna de un archivo, dando lugar a una expresión heterogénea de los números; lo cual puede producir verdaderos dolores de cabeza, sobre todo si el volumen de información es inmenso. ¿Cómo se puede manejar este tipo de errores en R?

Para analizar este problema más de cerca, propongo el siguiente ejemplito de juguete:

Se hizo un censo, y la información se conjuntó en una tabla de Excel con dos columnas, que se leyó en R de la manera siguiente:

tt <- read.csv2("heterogeneo.csv", stringsAsFactors=F)
tt
##   uno    dos
## 1   a    1.2
## 2   b    3.5
## 3   c    6.4
## 4   d   -7.2
## 5   e    5,3
## 6   f    6.8
## 7   g  0,001

Detección del problema

Se nota primeramente que hemos leído la infomación bloqueando la interpretación de los strings de caracteres como factores; esto es, precisamente para tratar los strings de caracteres justamente como strings de caracteres en todo el desarrollo del problema.

Se espera que la columna “dos” de la tabla leída sea de tipo numeric; veamos, sin embargo, qué es lo que resulta al consultar su clase:

## La clase de la columna dos:
class(tt$dos)
## [1] "character"

Esto indica que la lectura no se ha hecho adecuadamente. Al revisar con detalle, se observa que en los renglones 5 y 7 se han puesto comas decimales en lugar de puntos decimales. Esto pudiera no ser evidente en una tabla con un elevado número de renglones. En seguida muestro cómo detectar los lugares exactos del problema:

# Se forza una conversión a "numeric"
nn <- as.numeric(tt$dos)
## Warning: NAs introducidos por coerción
# La operación anterior arroja un **Warning**, pero no interrumpe los
# cálculos.
# En un gran número de renglones la siguiente impresión, posiblemente
# no mostraría los sitios del problema:
nn
## [1]  1.2  3.5  6.4 -7.2   NA  6.8   NA
# Para saber los índices (renglones) exactos de los elementos que han
# causado el problema, se hace así:
is.na(nn) # Vector de lógicos
## [1] FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE
which( is.na(nn) ) # Así se convierte el vector de lógicos a índices
## [1] 5 7

NOTA: El uso de la función which() aplicable a los vectores booleanos se ha ilustrado en un post previo: Transformación de selecciones booleanas en selecciones indexadas.

Solución

La solución del problema es sencilla. Se parte del hecho de que la columna se ha leído como un string de caracteres. La clave es simplemente convertir los caracteres “,” a “.”, en los strings que conforman el vector, de la siguiente manera:

# Guardamos el resultado en la misma columna:
tt$dos <- sub(",", ".", tt$dos, fixed = TRUE)
tt$dos
## [1] " 1.2"   " 3.5"   " 6.4"   " -7.2"  " 5.3"   " 6.8"   " 0.001"

En seguida se ejecuta la conversión de toda la columna a numeric:

# Nuevamente guardamos el resultado en la misma columna:
tt$dos <- as.numeric(tt$dos)
tt
##   uno    dos
## 1   a  1.200
## 2   b  3.500
## 3   c  6.400
## 4   d -7.200
## 5   e  5.300
## 6   f  6.800
## 7   g  0.001
# Y la clase de la columna es:
class(tt$dos)
## [1] "numeric"

Con esto se concluye el ejemplo.


Si quieres ver este mismo post en mis publicaciones de Rpubs:


viernes, 17 de julio de 2015

Transformación de selecciones booleanas a selecciones indexadas

[Lenguajes: R]

Problema

En R, una de las técnicas para seleccionar porciones de conjuntos de datos a partir de condiciones dadas, es por medio de la utilización de índices booleanos creados a partir de expresiones lógicas. Por ejemplo, si tenemos un vector numérico como el que sigue:

V <- c(18, 8, 5, 41, 8, 7)
V
## [1] 18  8  5 41  8  7

y se quiere saber para cuáles de estos números, su valor absoluto menos nueve es menor que tres, lo podemos hacer con la expresión lógica:

( L <- abs(V - 9) <= 3 )
## [1] FALSE  TRUE FALSE FALSE  TRUE  TRUE

Esta expresión, es un vector lógico, paralelo al original, que indica con el valor TRUE, los correspondientes valores de V que cumplen con la condición.

Para saber cuáles son los valores que cumplen con la condición, simplemente se usa como índice el vector lógico resultante, L, en el vector numérico original, V:

V[L]
## [1] 8 8 7

Sin embargo, a veces, estmos realmente interesados en los índices numéricos de los elementos de V que cumplen con la condición, esto es, 2, 5 y 6.

Solución

La función which() transforma los índices booleanos a índices numéricos enteros, de la siguiente manera:

which(L)
## [1] 2 5 6

Un resultado similar se puede obtener si se usa una secuencia numérica, de 1 hasta el número de elementos en V, esto es, 1, 2, …, length(V), y se sustraen los elementos marcados como verdaderos en L, como se muestra a continuación:

seq_along(V)[L]
## [1] 2 5 6
# o también:
seq_along(L)[L]
## [1] 2 5 6

Si quieres ver este mismo post en mis publicaciones de Rpubs:


viernes, 10 de julio de 2015

Gráfico de caída libre

[Lenguajes: R]

Problema

Dado que el lenguaje R tiene la posibilidad de manejar vectores de una manera muy natural, los problemas de la mecánica clásica se pueden resolver de manera bastante sencilla, con la posibilidad, incluso, de presentar los resultados gráficos muy atractivos.

En seguida propongo un problema sencillo.

Problema de mecánica clásica

De un edificio, a una altura de 15 m, se ha lanzado con un ángulo de 50 grados, un proyectil a una velocidad de 7 m/s. ¿Cuáles serán las alturas (coordenadas y) del proyectil a cada 0.5 m de distancia horizontal desde donde se lanzó y hasta los 11 m?

Se requiere además hacer una gráfica que muestre la trayectoria del proyectil.

Solución

Las ecuaciones físicas que gobiernan este fenómeno son las siguientes:

(Desgraciadamente Blogger falla en desplegar las ecuaciones de abajo, sugiero que vean este post en el PEGOTE de Rpubs , abajo.)

\[x=v_{0x}t+x_{0}\] \[y=-\frac{1}{2}gt^{2}+v_{0y}t+y_{0}\]
Aquí, $g$ es la aceleración de la gravedad, el parámetro \(t\) se refiere al tiempo, y la velocidad está descompuesta en sus componentes: \(v_{0x}\) y \(v_{0y}\).
g <- 9.81 # aceleracion gravedad
x0 <- 0   # x inicial
y0 <- 15  # y inicial
vi <- 7  # velocidad inicial
alphaD <- 50 # angulo-grados

y para encontrar las componentes de la velocidad:

# Se convierte el ángulo a radianes:
alpha <- (pi/180)*alphaD # angulo-radianes
(vox <- vi*cos(alpha)) # componente x de velocidad inicial
## [1] 4.499513
(voy <- vi*sin(alpha)) # componente y de velocidad inicial
## [1] 5.362311

Con esto es suficiente para proceder con el problema. Primeramente se obtienen las x para las que se desea hacer el cálculo, de la siguiente forma:

# desde 0 hasta 11 de 0.5 en 0.5:
las.x <- seq(from=0, to=11, by=0.5)

Nótese que en las fórmulas que gobiernan el fenómeno, dadas anteriormente, no se tiene \(y\) en función de \(x\), sino que las dos coordenadas dependen del parámetro \(t\), esto es, del tiempo. Para resolver este asunto simplemente se despeja en parámetro \(t\), en la ecuación de \(x\), y obtenemos:

\[t=(x-x_{0})/v_{0x}\]

Así, obtenemos los valores de \(t\) correspondientes a las \(x\), usando esta fórmula en R:

las.t <- (las.x - x0)/vox

Finalmente, encontramos las y correspondientes a las t, recien encontradas, aplicando la fórmula para y:

las.y <- -(g/2)*las.t^2 + voy*las.t + y0
# Los resultados:
las.x
##  [1]  0.0  0.5  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5
## [15]  7.0  7.5  8.0  8.5  9.0  9.5 10.0 10.5 11.0
las.y
##  [1] 15.0000000 15.5353081 15.9494790 16.2425125 16.4144087 16.4651675
##  [7] 16.3947891 16.2032734 15.8906203 15.4568299 14.9019022 14.2258372
## [13] 13.4286349 12.5102953 11.4708183 10.3102040  9.0284524  7.6255636
## [19]  6.1015373  4.4563738  2.6900730  0.8026348 -1.2059407

El gráfico se podría hacer con los resultados anteriores utilizando la función plot(), con su parámetro type=“l”, que corresponde a líneas, de la siguiente manera:

plot(las.x, las.y, type="l")

plot of chunk unnamed-chunk-6

Nótese la diferencia de escalas en los ejes. Un método más elegante consiste en utilizar la función curve(), diseñada específicamente para graficar funciones. Para ello, los cálculos anteriores deben ser transformados a funciones, de la manera que se muestra a continuación:

ft <- function(x) (x-x0)/vox # tiempo para una x dada
# Las 'y' en función de x:
y <- function(x) {t <- ft(x); -(g/2)*t^2 + voy*t + y0}
curve(y,xlim=c(0,11),asp=1, main="Lanzamiento proyectil")
abline(v=0,col="red") # linea vertical x=0
abline(h=0,col="red") # linea horizontal y=0
points(x0,y0,pch=19, col="red") # Punto inicial

plot of chunk unnamed-chunk-7


Si quieres ver este mismo post en mis publicaciones de Rpubs: