8 Programar en Julia

8.1 jl-files

Los jl-files son Scripts (archivos de texto) donde guardamos una secuencia de comandos. Son útiles para automatizar un bloque de comandos. Los Scripts operan sobre datos existentes y crean nuevos datos a partir de las operaciones contenidas (y guardados en el espacio de trabajo). Los Scripts pueden contener también funciones que ejecutan tareas específicas. Estos archivos son guardados con extensión .jl.

Los jl−files puede llevar comentarios, esto es bloques de texto que son ignorados por el compilador. Toda línea que empieza con signo de porcentaje (#) es considerada comentarios.

Para ejecutar un jl-file en el REPL (read–eval–print-loop) de Julia en la terminal usamos:

> include("myScript.jl")

El jl-file debe estar guardado en el directorio de trabajo.

8.2 Funciones de Usuario

  • Es una buena práctica dividir la tarea que queremos realizar con julia en tareas simples y asignar una función para cada tarea simple.

  • Son muy útiles para procedimiento repetidos.

La sintaxis de una función en Julia es:

"""
Documentación de la función
"""

function nombre_funcion(input1, input2,...) 

    comandos

    return output1, output2, ...

end

Ejemplo 1: Crear una función de nombre fx que evalúa un polinomio de grado 2.

"""
**fx** Evalúa un polinomio de segundo grado. Función aplica sólo a escalares.

Uso:

val = fx(x)
"""

function fx(x)    
    return 2*x^2 + 3*x - 5
end
?fx
**fx** Evalúa un polinomio de segundo grado. Función aplica sólo a escalares.

**Uso:**


val = fx(x)
f2 = fx(4)
39

Esta misma función puede usarse sobre vectores, matrices o arreglos de mayor dimensión usando la notación funcion.() (aplica la función a cada elemento).

x = rand(4,1)
y = fx.(x)
4×1 Array{Float64,2}:
 -3.30007741621653  
 -1.2607592332029083
 -1.1012791244536677
 -2.772979772220121 

Ejemplo 2: Crear una función de nombre stat, que calcula la media y la desviación estándar de un vector, y guardarla en el directorio de trabajo.

"""
**stat** Calcula la media y la desviación estándar de un vector x.

Uso:
mean, stdev = stat(x) 

"""
function stat(x)

    n     = length(x)
    mean  = sum(x)/n
    stdev = sqrt(sum(x.^2)/n - mean^2)   # Nota: elevamos al cuadrado cada elemento del vector (.^)
    
    return mean, stdev

end
?stat
**stat** Calcula la media y la desviación estándar de un vector x.

**Uso:**

mean, stdev = stat(x) 
xvec = 50*ones(100,1)+4*randn(100,1)

media, desv = stat(xvec)
(50.14366440248996, 3.659923232929554)
media
50.14366440248996

Funciones simples pueden crearse usando las dos siguientes opciones:

  • Usando notación matemática:
fx2(x) = 2*x^2 + 3*x - 5
fx2(5)
60
  • Usando el formato input ->:
fx3 = x -> 2*x^2 + 3*x - 5
fx3(5)
60

Nota Importante!!

  • Las funciones creadas Julia cuentan con la información provista como input y todas las variables del espacio de memoria (cómo variables globales). Por esto, y para tener un mejor control de los argumentos que está usando una función, es una buena práctica pasar como argumento explícito todo lo que la función requiera.
  • Todas las variables creadas al interior de la función son variables locales, esto es, sólo existen dentro de la función. Así, la función dejará en el espacio de trabajo sólo las variables que fueron pasadas por el return de la función.
  • Los argumentos (inputs) de una función pueden contener strings, matrices, vectores, escalares, y funciones.
  • Es posible además declarar funciones (varias) en un jl-file separado y llamarlo con include("misfunciones.jl") para que las funciones estén disponible en el espacio de trabajo.

Ejemplo 3: Crear una función de nombre fxalt que evalúa un polinomio de grado 2 tomando dos argumentos: \(x\) y los parámetros del polinomio:

"""
**fx* Evalúa un polinomio de segundo grado.

Uso:     
val = fxalt(x,params)

params: vector 3 x 1 de parámetros.
"""
function fxalt(x,param)

    a = param[1]
    b = param[2]
    c = param[3]

    return a*x^2 + b*x + c

end
pa = 3.0
pb = 3.0
pc = -5.0

p = [pa, pb, pc]
x = 4.0

val = fxalt(x,p)
println(val)
55.0

Una forma de transformar la función fxalt para que sólo dependa del argumento x es usar una función anónima.

pa = 2.0
pb = 3.0
pc = -5.0

p = [pa, pb, pc]

fx4 = x -> fxalt(x,p)  # fijamos p = [2, 3, -5]

fx4(4.0)
39.0

8.3 Loops

Los Loops son bucles que sirven para realizar operaciones de manera repetida. En Julia existen dos tipos de Loops:

  • For Loop: Realiza la misma acción un número determinado de veces. Su sintaxis es como sigue:
for expresión_iterable

    Comando 1 
    Comando 2
    Comando 3
    ...

end

Ejemplo 1: Calcular la suma acumulada de un vector:

n = 8
x = rand(n)
sumx = zeros(n)

sumx[1]= x[1] 

for i = 2:n
    sumx[i] = sumx[i-1]+x[i]
end

println(sumx)
[0.946697, 1.87125, 2.33302, 3.19241, 3.57936, 4.38677, 5.00578, 5.07728]

Comentarios:

  • Al igual que en el caso de las funciones, en Julia todas las variables que se crean al interior de un For Loop tienen un ambito local, es decir sólo existen dentro del loop. Para guardar los resultados del loop debemos declarar la función fuera del loop (inicializarla, como es el caso del vector sumx) para que está exista en memoria una vez concluido el loop.
  • Los loops operan repetidas veces con elementos iterables. Las secuencias creadas como inicio:incremento:fin o con la función range() son iterables. No obstante lo anterior, en general, cualquier arreglo en Julia es iterable.
iterable = 2:n
2:8
typeof(interable)
UndefVarError: interable not defined



Stacktrace:

 [1] top-level scope at In[16]:1

Un Arreglo formado de texto puede ser iterado (para ello se utiliza la opción in en el For Loop):

clase = ["Macro", "Micro", "Econometría"]
for i in clase
    println("La Materia es: $i")
end
La Materia es: Macro
La Materia es: Micro
La Materia es: Econometría

Existen una forma muy útil de construir Arreglos sobre la base de iterables de una manera sencilla. Es herramienta se llama list comprehension y utiliza el formato [función for var_iterable in objeto_iterable]. Por ejemplo, para calcular el cuadrado de cada elemento de un vector podemos usar (demás de la notación ):

x = [3.0, 6.0, 9.0, 8.0, 3.0]
sqrx = [i^2 for i in x]
5-element Array{Float64,1}:
  9.0
 36.0
 81.0
 64.0
  9.0

Alternativamente es posible usa la función map(función, x) para pasar una función escalar anónima por cada elemento de un arreglo:

funsqr(x) = x^2
sqrx2 = map(funsqr,x)
5-element Array{Float64,1}:
  9.0
 36.0
 81.0
 64.0
  9.0

o definiendo una función de forma anónima directamente en map():

sqrx2 = map(y -> y^2,x)
5-element Array{Float64,1}:
  9.0
 36.0
 81.0
 64.0
  9.0
  • While Loop: Realiza la misma acción hasta que una condición se cumpla.
while Condición

    Comando 1
    Comando 2
    Comando3
    ...
end

Ejemplo 2: Calcular la secuencia de número en la que cada numero precedente es la mitad del anterior. La secuencia termina cuando el número final sea menor que 0.05.

delta = 1
println(delta)
while delta > 0.05
    delta = delta/2
    println(delta)
end
1
0.5
0.25
0.125
0.0625
0.03125

Comentarios:

  • Julia es eficiente al utilizar loops.
  • Un for loop puede comportarse de forma descendente: for i=10:-1:1.
  • Podemos anidar varios loops. Por ejemplo:
m = 3
n = 2
H = zeros(m,n)

for i = 1:m
    for j = 1:n 
        H[i, j] = 1/(i+j-1)
    end
end

H
3×2 Array{Float64,2}:
 1.0       0.5     
 0.5       0.333333
 0.333333  0.25    

Para loops anidados, Julia tiene una notación más compacta que consta de un For Loop y varios iterables separados por coma, por ejemplo:

H2 = zeros(m,n)

for i in 1:m, j in 1:n 
    H2[i, j] = 1/(i+j-1)
end

H2
3×2 Array{Float64,2}:
 1.0       0.5     
 0.5       0.333333
 0.333333  0.25    

y también puede usar una list comprehension y escribir el el loop anterior de manera aún más compacta:

mult = [1/(i+j-1) for i in 1:m, j in 1:n]
3×2 Array{Float64,2}:
 1.0       0.5     
 0.5       0.333333
 0.333333  0.25    
  • Podemos utilizar un while loop como un for loop, sólo se necesita definir un contador. Por ejemplo:
iter = 1

while iter < 5 
    println(iter)
    iter += 1   # esto es equivalente a iter = iter + 1
end
1
2
3
4

8.4 Condicionales y Operadores Lógicos

Los condicionales sirven para ejecutar un comando si se cumple cierta condición. La sintaxis para condicionales es:

if condicion 1
    comando
elseif condicion 2 
    comando
elseif condicion 3 
    comando
else
    comando
end

donde condicion 1, condicion 2, etc son variable boolenada: trueo false. Para construir variables boolianas utilizamos los siguientes operadores lógicos:

  • < menor a
  • > mayor a
  • <= menor o igual a
  • >= mayor o igual a
  • == igual a
  • != no es igual a
  • | o: verdadero si al menos una es verdadera
  • || o: verdadero si al menos una es verdadera (no evalúa la segunda si la primera condición es verdadera)
  • & y: verdadero si todas son verdaderas
  • && y: verdadero si todas son verdaderas (no evalúa la segunda condición si la primera es falsa)
  • ! negativo
  • any verdadero si algún elemento de un Arreglo booleano es true
  • all verdadero si todos los elementos de un Arreglo booleano son true
  • isnan detecta valores NaN en una matriz
  • isempty detecta si es una matriz vacía.

Ejemplo 3:

A = randn()
println(A)
if A>0
    println("A es positivo")
elseif A==0
    println("A es cero")
else
    println("A es negativo")
end
2.202312539016582
A es positivo

Las condiciones son verificadas de manera secuencial. En cuando se cumple una condición, el comando if ya no continúa con las demás condiciones. Por ejemplo:

n = 0

if -5 > 0 
    n = 1
elseif -1 > 0 
    n = 2
elseif 3 > 0
    n = 3 
else
    n = 4
end

println(n)
3

Comentarios:

  • Matrices lógicas: dummy = (condición) genera una matriz de dummies si se cumple la condición.
  • Referenciación: A(condición) genera una matriz con los elementos de A que cumplen la condición.
  • Si se comparan elemento por elemento dos vectores o matrices, se utiliza la notación .operador.
  • Los condicionales if pueden estar anidados entre si y anidados dentro Loops.

Ejemplo del uso de matrices lógicas:

 A = [1, 4, 7, 2, 8, 5, 2, 7, 9]
 B = [2, 5, 1, 4, 8, 0, 3, 4, 3]
 
 dummy = A.>B
9-element BitArray{1}:
 false
 false
  true
 false
 false
  true
 false
  true
  true

Ejemplo de referenciación:

F = [A B]

subF1 = F[A.>B,:] # Alternativa 1
4×2 Array{Int64,2}:
 7  1
 5  0
 7  4
 9  3
subF2 = F[F[:,1].>F[:,2],:] # Alternativa 2
4×2 Array{Int64,2}:
 7  1
 5  0
 7  4
 9  3