# Algebra Lineal con Python

## Introducción

Una de las herramientas matemáticas más utilizadas en [machine learning](http://es.wikipedia.org/wiki/Machine_learning) y [data mining](http://es.wikipedia.org/wiki/Miner%C3%ADa_de_datos) es el [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal); por tanto, si queremos incursionar en el fascinante mundo del aprendizaje automático y el análisis de datos es importante reforzar los conceptos que forman parte de sus cimientos.

El [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) es una rama de las [matemáticas](http://es.wikipedia.org/wiki/Matem%C3%A1ticas) que es sumamente utilizada en el estudio de una gran variedad de ciencias, como ingeniería, finanzas, investigación operativa, entre otras. Es una extensión del [álgebra](http://es.wikipedia.org/wiki/%C3%81lgebra) que aprendemos en la escuela secundaria, hacia un mayor número de dimensiones; en lugar de trabajar con incógnitas a nivel de escalares comenzamos a trabajar con matrices y [vectores](http://es.wikipedia.org/wiki/Vector).

El estudio del [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) implica trabajar con varios objetos matemáticos, como ser:

* **Los Escalares**: Un *escalar* es un solo número, en contraste con la mayoría de los otros objetos estudiados en [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal), que son generalmente una colección de múltiples números.

* **Los [Vectores](http://es.wikipedia.org/wiki/Vector)**:Un *vector* es una serie de números. Los números tienen una orden preestablecido, y podemos identificar cada número individual por su índice en ese orden. Podemos pensar en los  *vectores* como la identificación de puntos en el espacio, con cada elemento que da la coordenada a lo largo de un eje diferente. Existen dos tipos de *vectores*, los *vectores de fila* y los *vectores de columna*. Podemos representarlos de la siguiente manera, dónde *f* es un vector de fila y *c* es un vector de columna:
$$f=\begin{bmatrix}0&1&-1\end{bmatrix} ;       c=\begin{bmatrix}0\\1\\-1\end{bmatrix}$$

* **Las Matrices**: Una *matriz* es un arreglo bidimensional de números (llamados entradas de la matriz) ordenados en filas (o renglones) y columnas, donde una fila es cada una de las líneas horizontales de la matriz y una columna es cada una de las líneas verticales. En una *matriz* cada elemento puede ser identificado utilizando dos índices, uno para la fila y otro para la columna en que se encuentra. Las podemos representar de la siguiente manera, *A* es una matriz de 3x2.
$$A=\begin{bmatrix}0 & 1& \\-1 & 2 \\ -2 & 3\end{bmatrix}$$

* **Los [Tensores](http://es.wikipedia.org/wiki/C%C3%A1lculo_tensorial)**:En algunos casos necesitaremos una matriz con más de dos ejes. En general, una serie de números dispuestos en una cuadrícula regular con un número variable de ejes es conocido como un *tensor*.

Sobre estos objetos podemos realizar las operaciones matemáticas básicas, como ser [adición](http://es.wikipedia.org/wiki/Adici%C3%B3n), [multiplicación](http://es.wikipedia.org/wiki/Multiplicaci%C3%B3n), [sustracción](http://es.wikipedia.org/wiki/Sustracci%C3%B3n) y división, es decir que vamos a poder sumar [vectores](http://es.wikipedia.org/wiki/Vector) con matrices, multiplicar escalares a [vectores](http://es.wikipedia.org/wiki/Vector) y demás.

## Librerías de Python para álgebra lineal

Los principales módulos que [Python](http://python.org/) nos ofrece para realizar operaciones de [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) son los siguientes:

* **[Numpy](http://www.numpy.org/)**: El popular paquete matemático de [Python](http://python.org/), nos va a permitir crear *[vectores](http://es.wikipedia.org/wiki/Vector)*, *matrices* y *[tensores](http://es.wikipedia.org/wiki/C%C3%A1lculo_tensorial)* con suma facilidad.

* **[numpy.linalg](http://docs.scipy.org/doc/numpy/reference/routines.linalg.html)**: Este es un submodulo dentro de [Numpy](http://www.numpy.org/) con un gran número de funciones para resolver ecuaciones de [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal).

* **[scipy.linalg](http://docs.scipy.org/doc/scipy/reference/tutorial/linalg.html)**: Este submodulo del paquete científico [Scipy](http://docs.scipy.org/doc/scipy/reference/index.html) es muy similar al anterior, pero con algunas más funciones y optimaciones.

* **[Sympy](http://www.sympy.org/es/)**: Esta librería nos permite trabajar con matemática simbólica, convierte a [Python](http://python.org/) en un [sistema algebraico computacional](http://es.wikipedia.org/wiki/Sistema_algebraico_computacional). Nos va a permitir trabajar con ecuaciones y fórmulas simbólicamente, en lugar de numéricamente.

* **[CVXOPT](http://cvxopt.org/)**: Este módulo nos permite resolver problemas de optimizaciones de [programación lineal](http://es.wikipedia.org/wiki/Programaci%C3%B3n_lineal).

* **[PuLP](http://pythonhosted.org//PuLP/)**: Esta librería nos permite crear modelos de [programación lineal](http://es.wikipedia.org/wiki/Programaci%C3%B3n_lineal) en forma muy sencilla con [Python](http://python.org/).

## Operaciones básicas

### Vectores

Un *[vector](http://es.wikipedia.org/wiki/Vector)* de largo `n` es una secuencia (o *array*, o *tupla*) de `n` números. La solemos escribir como x=(x1,...,xn) o x=[x1,...,xn]

En [Python](http://python.org/), un *[vector](http://es.wikipedia.org/wiki/Vector)* puede ser representado con una simple *lista*, o con un *array* de [Numpy](http://www.numpy.org/); siendo preferible utilizar esta última opción.

```python
# Vector como lista de Python
v1 = [2, 4, 6]
v1
```

    [2, 4, 6]

```python
# Vectores con numpy
import numpy as np

v2 = np.ones(3) # vector de solo unos.
v2
```

    array([1., 1., 1.])

```python
v3 = np.array([1, 3, 5]) # pasando una lista a las arrays de numpy
v3
```

    array([1, 3, 5])

```python
v4 = np.arange(1, 8) # utilizando la funcion arange de numpy
v4
```

    array([1, 2, 3, 4, 5, 6, 7])

### Representación gráfica

Tradicionalmente, los *[vectores](http://es.wikipedia.org/wiki/Vector)* son representados visualmente como flechas que parten desde el origen hacia un punto.

Por ejemplo, si quisiéramos representar graficamente a los vectores v1=[2, 4], v2=[-3, 3] y v3=[-4, -3.5], podríamos hacerlo de la siguiente manera.

```python
import matplotlib.pyplot as plt
from warnings import filterwarnings

%matplotlib inline
filterwarnings('ignore') # Ignorar warnings
```

```python
def move_spines():
    """Crea la figura de pyplot y los ejes. Mueve las lineas de la izquierda y de abajo
    para que se intersecten con el origen. Elimina las lineas de la derecha y la de arriba.
    Devuelve los ejes."""
    fix, ax = plt.subplots()
    for spine in ["left", "bottom"]:
        ax.spines[spine].set_position("zero")

    for spine in ["right", "top"]:
        ax.spines[spine].set_color("none")

    return ax

def vect_fig():
    """Genera el grafico de los vectores en el plano"""
    ax = move_spines()

    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.grid()
    vecs = [[2, 4], [-3, 3], [-4, -3.5]] # lista de vectores
    for v in vecs:
        ax.annotate(" ", xy=v, xytext=[0, 0],
                   arrowprops=dict(facecolor="blue",
                                  shrink=0,
                                  alpha=0.7,
                                  width=0.5))
        ax.text(1.1 * v[0], 1.1 * v[1], v)
```

```python
vect_fig() # crea el gráfico
```

### Operaciones con vectores

Las operaciones más comunes que utilizamos cuando trabajamos con *[vectores](http://es.wikipedia.org/wiki/Vector)* son la *suma*, la *resta* y la *multiplicación por escalares*.

Cuando *sumamos* dos *[vectores](http://es.wikipedia.org/wiki/Vector)*, vamos sumando elemento por elemento de cada
*[vector](http://es.wikipedia.org/wiki/Vector)*.

$$ \begin{split}x + y
=
\left[
\begin{array}{c}
    x_1 \\
    x_2 \\
    \vdots \\
    x_n
\end{array}
\right]
+
\left[
\begin{array}{c}
     y_1 \\
     y_2 \\
    \vdots \\
     y_n
\end{array}
\right]
:=
\left[
\begin{array}{c}
    x_1 + y_1 \\
    x_2 + y_2 \\
    \vdots \\
    x_n + y_n
\end{array}
\right]\end{split}$$

De forma similar funciona la operación de resta.

$$ \begin{split}x - y
=
\left[
\begin{array}{c}
    x_1 \\
    x_2 \\
    \vdots \\
    x_n
\end{array}
\right]
-
\left[
\begin{array}{c}
     y_1 \\
     y_2 \\
    \vdots \\
     y_n
\end{array}
\right]
:=
\left[
\begin{array}{c}
    x_1 - y_1 \\
    x_2 - y_2 \\
    \vdots \\
    x_n - y_n
\end{array}
\right]\end{split}$$

La *multiplicación por escalares* es una operación que toma a un número $\gamma$, y a un *[vector](http://es.wikipedia.org/wiki/Vector)* $x$ y produce un nuevo *[vector](http://es.wikipedia.org/wiki/Vector)* donde cada elemento del vector $x$ es multiplicado por el número $\gamma$.

$$\begin{split}\gamma x
:=
\left[
\begin{array}{c}
    \gamma x_1 \\
    \gamma x_2 \\
    \vdots \\
    \gamma x_n
\end{array}
\right]\end{split}$$

En [Python](http://python.org/) podríamos realizar estas operaciones en forma muy sencilla:

```python
# Ejemplo en Python
x = np.arange(1, 5)
y = np.array([2, 4, 6, 8])
x, y
```

    (array([1, 2, 3, 4]), array([2, 4, 6, 8]))

```python
# sumando dos vectores numpy
x + y
```

    array([ 3,  6,  9, 12])

```python
# restando dos vectores
x - y
```

    array([-1, -2, -3, -4])

```python
# multiplicando por un escalar
x * 2
```

    array([2, 4, 6, 8])

```python
y * 3
```

    array([ 6, 12, 18, 24])

#### Producto escalar o interior

El [producto escalar](https://es.wikipedia.org/wiki/Producto_escalar) de dos *[vectores](http://es.wikipedia.org/wiki/Vector)* se define como la suma de los productos de sus elementos, suele representarse matemáticamente como < x, y > o x'y, donde x e y son dos vectores.

$$< x, y > := \sum_{i=1}^n x_i y_i$$

Dos *[vectores](http://es.wikipedia.org/wiki/Vector)* son ortogonales o perpendiculares cuando forman ángulo recto entre sí. Si el producto escalar de dos vectores es cero, ambos vectores son ortogonales.

Adicionalmente, todo [producto escalar](https://es.wikipedia.org/wiki/Producto_escalar) induce una [norma](https://es.wikipedia.org/wiki/Norma_vectorial) sobre el espacio en el que está definido, de la siguiente manera:

$$\| x \| := \sqrt{< x, x>} := \left( \sum_{i=1}^n x_i^2 \right)^{1/2}$$

En [Python](http://python.org/) lo podemos calcular de la siguiente forma:

```python
# Calculando el producto escalar de los vectores x e y
np.dot(x, y)
```

    60

```python
# o lo que es lo mismo, que:
sum(x * y)
```

    60

```python
# Calculando la norma del vector X
np.linalg.norm(x)
```

    5.477225575051661

```python
# otra forma de calcular la norma de x
np.sqrt(np.dot(x, x))
```

    5.477225575051661

```python
# vectores ortogonales
v1 = np.array([3, 4])
v2 = np.array([4, -3])

np.dot(v1, v2)
```

    0

### Matrices

Las matrices son una forma clara y sencilla de organizar los datos para su uso en operaciones lineales.

Una matriz `n × k` es una agrupación rectangular de números con n filas y k columnas; se representa de la siguiente forma:

$$\begin{split}A =
\left[
\begin{array}{cccc}
    a_{11} & a_{12} & \cdots & a_{1k} \\
    a_{21} & a_{22} & \cdots & a_{2k} \\
    \vdots & \vdots &  & \vdots \\
    a_{n1} & a_{n2} & \cdots & a_{nk}
\end{array}
\right]\end{split}$$

En la matriz A, el símbolo $a_{nk}$ representa el elemento  n-ésimo de la fila en la k-ésima columna. La matriz A también puede ser llamada un *[vector](http://es.wikipedia.org/wiki/Vector)* si cualquiera de n o k son iguales a 1. En el caso de n=1, A se llama un *[vector](http://es.wikipedia.org/wiki/Vector) fila*, mientras que en el caso de k=1 se denomina un *[vector](http://es.wikipedia.org/wiki/Vector) columna*.

Las matrices se utilizan para múltiples aplicaciones y sirven, en particular, para representar los coeficientes de los sistemas de ecuaciones lineales o para representar transformaciones lineales dada una base. Pueden sumarse, multiplicarse y descomponerse de varias formas.

### Operaciones con matrices

Al igual que con los *[vectores](http://es.wikipedia.org/wiki/Vector)*, que no son más que un caso particular, las matrices se pueden *sumar*, *restar* y la *multiplicar por escalares*.

Multiplicacion por escalares:
$$\begin{split}\gamma A
\left[
\begin{array}{ccc}
    a_{11} &  \cdots & a_{1k} \\
    \vdots & \vdots  & \vdots \\
    a_{n1} &  \cdots & a_{nk} \\
\end{array}
\right]
:=
\left[
\begin{array}{ccc}
    \gamma a_{11} & \cdots & \gamma a_{1k} \\
    \vdots & \vdots & \vdots \\
    \gamma a_{n1} & \cdots & \gamma a_{nk} \\
\end{array}
\right]\end{split}$$

Suma de matrices:

 $$\begin{split}A + B =
\left[
\begin{array}{ccc}
    a_{11} & \cdots & a_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} & \cdots & a_{nk} \\
\end{array}
\right]
+
\left[
\begin{array}{ccc}
    b_{11} & \cdots & b_{1k} \\
    \vdots & \vdots & \vdots \\
    b_{n1} & \cdots & b_{nk} \\
\end{array}
\right]
:=
\left[
\begin{array}{ccc}
    a_{11} + b_{11} &  \cdots & a_{1k} + b_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} + b_{n1} &  \cdots & a_{nk} + b_{nk} \\
\end{array}
\right]\end{split}$$

Resta de matrices:
$$\begin{split}A - B =
\left[
\begin{array}{ccc}
    a_{11} & \cdots & a_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} & \cdots & a_{nk} \\
\end{array}
\right]-
\left[
\begin{array}{ccc}
    b_{11} & \cdots & b_{1k} \\
    \vdots & \vdots & \vdots \\
    b_{n1} & \cdots & b_{nk} \\
\end{array}
\right]
:=
\left[
\begin{array}{ccc}
    a_{11} - b_{11} &  \cdots & a_{1k} - b_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} - b_{n1} &  \cdots & a_{nk} - b_{nk} \\
\end{array}
\right]\end{split}$$

Para los casos de suma y resta, hay que tener en cuenta que solo se pueden sumar o restar matrices que tengan las mismas dimensiones, es decir que si tengo una matriz A de dimensión 3x2 (3 filas y 2 columnas) solo voy a poder sumar o restar la matriz B si esta también tiene 3 filas y 2 columnas.

```python
# Ejemplo en Python
A = np.array([[1, 3, 2],
              [1, 0, 0],
              [1, 2, 2]])

B = np.array([[1, 0, 5],
              [7, 5, 0],
              [2, 1, 1]])
```

```python
# suma de las matrices A y B
A + B
```

    array([[2, 3, 7],
           [8, 5, 0],
           [3, 3, 3]])

```python
# resta de matrices
A - B
```

    array([[ 0,  3, -3],
           [-6, -5,  0],
           [-1,  1,  1]])

```python
# multiplicando matrices por escalares
A * 2
```

    array([[2, 6, 4],
           [2, 0, 0],
           [2, 4, 4]])

```python
B * 3
```

    array([[ 3,  0, 15],
           [21, 15,  0],
           [ 6,  3,  3]])

```python
# ver la dimension de una matriz
A.shape
```

    (3, 3)

```python
# ver cantidad de elementos de una matriz
A.size
```

    9

#### Multiplicacion o Producto de matrices

La regla para la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices) generaliza la idea del [producto interior](https://es.wikipedia.org/wiki/Producto_escalar) que vimos con los [vectores](http://es.wikipedia.org/wiki/Vector); y esta diseñada para facilitar las operaciones lineales básicas.
Cuando [multiplicamos matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), el número de columnas de la primera matriz debe ser igual al número de filas de la segunda matriz; y el resultado de esta multiplicación va a tener el mismo número de filas que la primer matriz y el número de la columnas de la segunda matriz. Es decir, que si yo tengo una matriz A de dimensión 3x4 y la multiplico por una matriz B de dimensión 4x2, el resultado va a ser una matriz C de dimensión 3x2.

Algo a tener en cuenta a la hora de [multiplicar matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices) es que la propiedad [connmutativa](https://es.wikipedia.org/wiki/Conmutatividad) no se cumple. AxB no es lo mismo que BxA.

Veamos los ejemplos en [Python](http://python.org/).

```python
# Ejemplo multiplicación de matrices
A = np.arange(1, 13).reshape(3, 4) #matriz de dimension 3x4
A
```

    array([[ 1,  2,  3,  4],
           [ 5,  6,  7,  8],
           [ 9, 10, 11, 12]])

```python
B = np.arange(8).reshape(4,2) #matriz de dimension 4x2
B
```

    array([[0, 1],
           [2, 3],
           [4, 5],
           [6, 7]])

```python
# Multiplicando A x B
A.dot(B) #resulta en una matriz de dimension 3x2
```

    array([[ 40,  50],
           [ 88, 114],
           [136, 178]])

```python
# Multiplicando B x A

B.dot(A)
```

Este ultimo ejemplo vemos que la propiedad conmutativa no se cumple, es más, [Python](http://python.org/) nos arroja un error, ya que el número de columnas de B no coincide con el número de filas de A, por lo que ni siquiera se puede realizar la multiplicación de B x A.

Para una explicación más detallada del proceso de [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), pueden consultar el siguiente [tutorial](http://www.mathsisfun.com/algebra/matrix-multiplying.html).

#### La matriz identidad,  la matriz inversa,  la matrix transpuesta y el determinante

La [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es el elemento neutro en la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), es el equivalente al número 1. Cualquier matriz multiplicada por la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) nos da como resultado la misma matriz. La [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) (tiene siempre el mismo número de filas que de columnas); y su diagonal principal se compone de todos elementos 1 y el resto de los elementos se completan con 0. Suele representase con la letra I

Por ejemplo la matriz identidad de 3x3 sería la siguiente:

$$I=\begin{bmatrix}1 & 0 & 0 & \\0 & 1 & 0\\ 0 & 0 & 1\end{bmatrix}$$

Ahora que conocemos el concepto de la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad), podemos llegar al concepto de la [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible). Si tenemos una matriz A, la [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) de A, que se representa como $A^{-1}$ es aquella [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) que hace que la multiplicación $A$x$A^{-1}$ sea igual a la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) I. Es decir que es la matriz recíproca de A.

$$A × A^{-1} = A^{-1} × A = I$$

Tener en cuenta que esta [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) en muchos casos puede no existir.En este caso se dice que la matriz es singular o degenerada. Una matriz es singular si y solo si su determinante es nulo.

El determinante es un número especial que puede calcularse sobre las [matrices cuadradas](https://es.wikipedia.org/wiki/Matriz_cuadrada). Se calcula como la suma de los productos de las diagonales de la matriz en una dirección menos la suma de los productos de las diagonales en la otra dirección. Se represente con el símbolo |A|.

$$A=\begin{bmatrix}a_{11} & a_{12} & a_{13} & \\a_{21} & a_{22} & a_{23} & \\ a_{31} & a_{32} & a_{33} & \end{bmatrix}$$

$$|A| =
     (a_{11} a_{22} a_{33}
   + a_{12} a_{23} a_{31}
   + a_{13} a_{21} a_{32} )
   - (a_{31} a_{22} a_{13}
   + a_{32} a_{23} a_{11}
   + a_{33} a_{21} a_{12})
 $$

Por último, la [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta) es aquella en que las filas se transforman en columnas y las columnas en filas. Se representa con el símbolo $A^\intercal$

$$\begin{bmatrix}a & b & \\c & d & \\ e & f & \end{bmatrix}^T:=\begin{bmatrix}a & c & e &\\b & d & f & \end{bmatrix}$$

Ejemplos en [Python](http://python.org/):

```python
# Creando una matriz identidad de 2x2
I = np.eye(2)
I
```

    array([[1., 0.],
           [0., 1.]])

```python
# Multiplicar una matriz por la identidad nos da la misma matriz
A = np.array([[4, 7],
              [2, 6]])
A
```

    array([[4, 7],
           [2, 6]])

```python
A.dot(I) # AxI = A
```

    array([[4., 7.],
           [2., 6.]])

```python
# Calculando el determinante de la matriz A
np.linalg.det(A)
```

    10.000000000000002

```python
# Calculando la inversa de A.
A_inv = np.linalg.inv(A)
A_inv
```

    array([[ 0.6, -0.7],
           [-0.2,  0.4]])

```python
# A x A_inv nos da como resultado I.
A.dot(A_inv)
```

    array([[ 1.00000000e+00, -1.11022302e-16],
           [ 1.11022302e-16,  1.00000000e+00]])

```python
# Trasponiendo una matriz
A = np.arange(6).reshape(3, 2)
A
```

    array([[0, 1],
           [2, 3],
           [4, 5]])

```python
np.transpose(A)
```

    array([[0, 2, 4],
           [1, 3, 5]])

### Sistemas de ecuaciones lineales

Una de las principales aplicaciones del [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) consiste en resolver problemas de sistemas de ecuaciones lineales.

Una [ecuación lineal](https://es.wikipedia.org/wiki/Ecuaci%C3%B3n_de_primer_grado) es una ecuación que solo involucra sumas y restas de una variable o mas variables a la primera potencia. Es la ecuación de la línea recta.Cuando nuestro problema esta representado por más de una [ecuación lineal](https://es.wikipedia.org/wiki/Ecuaci%C3%B3n_de_primer_grado), hablamos de un [sistema de ecuaciones lineales](http://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales). Por ejemplo, podríamos tener un sistema de dos ecuaciones con dos incógnitas como el siguiente:

$$ x - 2y = 1$$
$$3x + 2y = 11$$

La idea es encontrar el valor de $x$ e $y$ que resuelva ambas ecuaciones. Una forma en que podemos hacer esto, puede ser representando graficamente ambas rectas y buscar los puntos en que las rectas se cruzan.

En [Python](http://python.org/) esto se puede hacer en forma muy sencilla con la ayuda de [matplotlib](http://matplotlib.org/).

```python
# graficando el sistema de ecuaciones.
x_vals = np.linspace(0, 5, 50) # crea 50 valores entre 0 y 5
plt.plot(x_vals, (1 - x_vals)/-2) # grafica x - 2y = 1
plt.plot(x_vals, (11 - (3*x_vals))/2) # grafica 3x + 2y = 11
plt.axis(ymin = 0)
```

x - 2y = 1
x - 2y  -x = 1 -x
-2y = 1 -x
-2y /2  = (1 - x)/2
-y = (1-x)/2
-y * (-1) = (1-x)/2  * (-1)
y = (1-x)/(-2)
y = -(1-x)/2 = (-1-(-x))/2 = (-1 + x)/2 = (x-1)/2

Luego de haber graficado las funciones, podemos ver que ambas rectas se cruzan en el punto (3, 1), es decir que la solución de nuestro sistema sería $x=3$ e $y=1$. En este caso, al tratarse de un sistema simple y con solo dos incógnitas, la solución gráfica puede ser de utilidad, pero para sistemas más complicados se necesita una solución numérica, es aquí donde entran a jugar las matrices.

Ese mismo sistema se podría representar como una ecuación matricial de la siguiente forma:

$$\begin{bmatrix}1 & -2 & \\3 & 2 & \end{bmatrix} \begin{bmatrix}x & \\y & \end{bmatrix} = \begin{bmatrix}1 & \\11 & \end{bmatrix}$$

Lo que es lo mismo que decir que la matriz A por la matriz $x$ nos da como resultado el [vector](http://es.wikipedia.org/wiki/Vector) b.

$$ Ax = b$$

En este caso, ya sabemos el resultado de $x$, por lo que podemos comprobar que nuestra solución es correcta realizando la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices).

```python
# Comprobando la solucion con la multiplicación de matrices.
A = np.array([[1., -2.],
              [3., 2.]])
x = np.array([[3.],[1.]])

A.dot(x)
```

    array([[ 1.],
           [11.]])

Para resolver en forma numérica los [sistema de ecuaciones](http://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), existen varios métodos:

* **El método de sustitución**: El cual consiste en despejar en una de las ecuaciones cualquier incógnita, preferiblemente la que tenga menor coeficiente y a continuación sustituirla en otra ecuación por su valor.

* **El método de igualacion**: El cual se puede entender como un caso particular del método de sustitución en el que se despeja la misma incógnita en dos ecuaciones y a continuación se igualan entre sí la parte derecha de ambas ecuaciones.

* **El método de reduccion**: El procedimiento de este método consiste en transformar una de las ecuaciones (generalmente, mediante productos), de manera que obtengamos dos ecuaciones en la que una misma incógnita aparezca con el mismo coeficiente y distinto signo. A continuación, se suman ambas ecuaciones produciéndose así la reducción o cancelación de dicha incógnita, obteniendo una ecuación con una sola incógnita, donde el método de resolución es simple.

* **El método gráfico**: Que consiste en construir el gráfica de cada una de las ecuaciones del sistema. Este método (manualmente aplicado) solo resulta eficiente en el plano cartesiano (solo dos incógnitas).

* **El método de Gauss**: El método de eliminación de Gauss o simplemente método de Gauss consiste en convertir un sistema lineal de n ecuaciones con n incógnitas, en uno escalonado, en el que la primera ecuación tiene n incógnitas, la segunda ecuación tiene n - 1 incógnitas, ..., hasta la última ecuación, que tiene 1 incógnita. De esta forma, será fácil partir de la última ecuación e ir subiendo para calcular el valor de las demás incógnitas.

* **El método de Eliminación de Gauss-Jordan**: El cual es una variante del método anterior, y consistente en triangular la matriz aumentada del sistema mediante transformaciones elementales, hasta obtener ecuaciones de una sola incógnita.

* **El método de Cramer**: El cual consiste en aplicar la [regla de Cramer](http://es.wikipedia.org/wiki/Regla_de_Cramer) para resolver el sistema. Este método solo se puede aplicar cuando la matriz de coeficientes del sistema es cuadrada y de determinante no nulo.

La idea no es explicar cada uno de estos métodos, sino saber que existen y que [Python](http://python.org/) nos hacer la vida mucho más fácil, ya que para resolver un [sistema de ecuaciones](http://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) simplemente debemos llamar a la función `solve()`.

Por ejemplo, para resolver este sistema de 3 ecuaciones y 3 incógnitas.

$$ x + 2y + 3z = 6$$
$$ 2x + 5y + 2z = 4$$
$$ 6x - 3y + z = 2$$

Primero armamos la matriz A de coeficientes y la matriz b de resultados y luego utilizamos `solve()` para resolverla.

```python
# Creando matriz de coeficientes
A = np.array([[1, 2, 3],
              [2, 5, 2],
              [6, -3, 1]])
A
```

    array([[ 1,  2,  3],
           [ 2,  5,  2],
           [ 6, -3,  1]])

```python
# Creando matriz de resultados
b = np.array([6, 4, 2])
b
```

    array([6, 4, 2])

```python
# Resolviendo sistema de ecuaciones
x = np.linalg.solve(A, b)
x
```

    array([-1.48029737e-16, -1.48029737e-16,  2.00000000e+00])

```python
# Comprobando la solucion
A.dot(x) == b
```

    array([False,  True,  True])

### Programación lineal

La [programación lineal](http://es.wikipedia.org/wiki/Programaci%C3%B3n_lineal) estudia las situaciones en las que se exige maximizar o minimizar funciones que se encuentran sujetas a determinadas restricciones.

Consiste en optimizar (minimizar o maximizar) una función lineal, denominada función objetivo, de tal forma que las variables de dicha función estén sujetas a una serie de restricciones que expresamos mediante un [sistema de inecuaciones lineales](http://es.wikipedia.org/wiki/Inecuaci%C3%B3n#Sistema_de_inecuaciones).

Para resolver un problema de programación lineal, debemos seguir los siguientes pasos:

1. Elegir las incógnitas.

2. Escribir la función objetivo en función de los datos del problema.

3. Escribir las restricciones en forma de sistema de inecuaciones.

4. Averiguar el conjunto de soluciones factibles representando gráficamente las restricciones.

5. Calcular las coordenadas de los vértices del recinto de soluciones factibles (si son pocos).

6. Calcular el valor de la función objetivo en cada uno de los vértices para ver en cuál de ellos presenta el valor máximo o mínimo según nos pida el problema (hay que tener en cuenta aquí la posible no existencia de solución).

Veamos un ejemplo y como [Python](http://python.org/) nos ayuda a resolverlo en forma sencilla.

Supongamos que tenemos la siguiente *función objetivo*:

$$f(x_{1},x_{2})= 50x_{1} + 40x_{2}$$

y las siguientes *restricciones*:

$$x_{1} + 1.5x_{2} \leq 750$$
$$2x_{1} + x_{2} \leq 1000$$
$$x_{1} \geq 0$$
$$x_{2} \geq 0$$

Podemos resolverlo utilizando [PuLP](http://pythonhosted.org//PuLP/), [CVXOPT](http://cvxopt.org/) o graficamente (con [matplotlib](http://matplotlib.org/)) de la siguiente forma.

```python
!python -m pip install pulp
```

    Requirement already satisfied: pulp in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (2.6.0)

```python
!python -m pip install --upgrade pip
```

    Requirement already satisfied: pip in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (21.3.1)

```python
!pip install PyHamcrest
```

    Requirement already satisfied: PyHamcrest in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (2.0.2)

```python
# Resolviendo la optimizacion con pulp
from pulp import *

# declarando las variables
x1 = LpVariable("x1", 0, 800)   # 0<= x1 <= 40
x2 = LpVariable("x2", 0, 1000) # 0<= x2 <= 1000

# definiendo el problema
prob = LpProblem("problem", LpMaximize)

# definiendo las restricciones
prob += x1+1.5*x2 <= 750
prob += 2*x1+x2 <= 1000
prob += x1>=0
prob += x2>=0

# definiendo la funcion objetivo a maximizar
prob += 50*x1+40*x2

# resolviendo el problema
status = prob.solve(use_mps=False)
LpStatus[status]

# imprimiendo los resultados
(value(x1), value(x2))
```

    (375.0, 250.0)

```python
!pip install cvxopt
```

    Requirement already satisfied: cvxopt in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (1.2.7)

```python
# Resolviendo el problema con cvxopt
from cvxopt import matrix, solvers

A = matrix([[-1., -2., 1., 0.], # columna de x1
            [-1.5, -1., 0., 1.]]) # columna de x2
b = matrix([750., 1000., 0., 0.]) # resultados
c = matrix([50., 40.]) # funcion objetivo

# resolviendo el problema
sol=solvers.lp(c,A,b)
```

         pcost       dcost       gap    pres   dres   k/t
     0: -2.5472e+04 -3.6797e+04  5e+03  0e+00  3e-01  1e+00
     1: -2.8720e+04 -2.9111e+04  1e+02  5e-16  9e-03  2e+01
     2: -2.8750e+04 -2.8754e+04  1e+00  3e-16  9e-05  2e-01
     3: -2.8750e+04 -2.8750e+04  1e-02  4e-17  9e-07  2e-03
     4: -2.8750e+04 -2.8750e+04  1e-04  2e-16  9e-09  2e-05
    Optimal solution found.

```python
# imprimiendo la solucion.
print('{0:.2f}, {1:.2f}'.format(sol['x'][0]*-1, sol['x'][1]*-1))
```

    375.00, 250.00

```python
# Resolviendo la optimizacion graficamente.
x_vals = np.linspace(0, 800, 10) # 10 valores entre 0 y 800
plt.plot(x_vals, ((750 - x_vals)/1.5)) # grafica x1 + 1.5x2 = 750
plt.plot(x_vals, (1000 - 2*x_vals)) # grafica 2x1 + x2 = 1000
plt.axis(ymin = 0)
```

Como podemos ver en el gráfico, ambas rectas se cruzan en la solución óptima, x1=375 y x2=250.

Con esto termino esta introducción al [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) con [Python](http://python.org/).

## Campos

Un Campo, $F$, es una [estructura algebraica](https://es.wikipedia.org/wiki/Estructura_algebraica) en la cual las operaciones de adición y [multiplicación](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n) se pueden realizar y cumplen con las siguientes propiedades:

1. La [propiedad conmutativa](https://es.wikipedia.org/wiki/Conmutatividad) tanto para la adición como para la [multiplicación](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n); es decir: $a + b = b + a$; y $a \cdot b = b \cdot a$; para todo $a, b \in F$

2. La propiedad asociativa, tanto para la adición como para la [multiplicación](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n); es decir: $(a + b) + c = a + (b + c)$; y $(a \cdot b) \cdot c = a \cdot (b \cdot c)$; para todo $a, b, c \in F$

3. La [propiedad distributiva](https://es.wikipedia.org/wiki/Distributividad) de la [multiplicación](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n) sobre la adición; es decir: $a \cdot (b + c) = a \cdot b + a \cdot c$; para todo $a, b, c \in F$

4. La existencia de un *[elemento neutro](https://es.wikipedia.org/wiki/Elemento_neutro)* tanto para la adición como para la [multiplicación](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n); es decir: $a + 0 = a$; y $a \cdot 1 = a$; para todo $a \in F$.

5. La existencia de un *[elemento inverso](https://es.wikipedia.org/wiki/Elemento_sim%C3%A9trico)* tanto para la adición como para la [multiplicación](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n); es decir: $a + (-a) = 0$; y $a \cdot a^{-1} = 1$; para todo $a \in F$ y $a \ne 0$.

Dos de los Campos más comunes con los que nos vamos a encontrar al trabajar en problemas de [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html), van a ser el [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de los [números reales](https://es.wikipedia.org/wiki/N%C3%BAmero_real), $\mathbb{R}$; y el [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de los [números complejos](http://relopezbriega.github.io/blog/2015/10/12/numeros-complejos-con-python/), $\mathbb{C}$.

## Vectores

Muchas nociones físicas, tales como las fuerzas, velocidades y aceleraciones, involucran una magnitud (el valor de la fuerza, velocidad o aceleración) y una dirección. Cualquier entidad que involucre magnitud y dirección se llama [vector](http://es.wikipedia.org/wiki/Vector). Los [vectores](http://es.wikipedia.org/wiki/Vector) se representan por flechas en las que la longitud de ellas define la magnitud; y la dirección de la flecha representa la dirección del [vector](http://es.wikipedia.org/wiki/Vector). Podemos pensar en los [vectores](http://es.wikipedia.org/wiki/Vector) como una serie de números. Éstos números tienen una orden preestablecido, y podemos identificar cada número individual por su índice en ese orden. Los [vectores](http://es.wikipedia.org/wiki/Vector) identifican puntos en el espacio, en donde cada elemento representa una coordenada del eje en el espacio. La típica forma de representarlos es la siguiente:

$$v = \left[ \begin{array}{c} x_1 \\ x_2 \\ \vdots \\ x_n \end{array} \right]$$

Geométricamente podemos representarlos del siguiente modo en el plano de 2 dimensiones:

```python
!pip install scipy
```

    Requirement already satisfied: scipy in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (1.7.3)
    Requirement already satisfied: numpy<1.23.0,>=1.16.5 in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (from scipy) (1.21.4)

```python

!pip install sympy
```

    Requirement already satisfied: sympy in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (1.9)
    Requirement already satisfied: mpmath>=0.19 in c:\users\admin\appdata\local\programs\python\python37\lib\site-packages (from sympy) (1.2.1)

```python
# <!-- collapse=True -->
# importando modulos necesarios
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg
import scipy.linalg as la
import sympy

# imprimir con notación matemática.
sympy.init_printing(use_latex='mathjax')
```

```python
# <!-- collapse=True -->
# graficando vector en R^2 [2, 4]
def move_spines():
    """Crea la figura de pyplot y los ejes. Mueve las lineas de la izquierda
    y de abajo para que se intersecten con el origen. Elimina las lineas de
    la derecha y la de arriba. Devuelve los ejes."""
    fix, ax = plt.subplots()
    for spine in ["left", "bottom"]:
        ax.spines[spine].set_position("zero")

    for spine in ["right", "top"]:
        ax.spines[spine].set_color("none")

    return ax

def vect_fig(vector, color):
    """Genera el grafico de los vectores en el plano"""
    v = vector
    ax.annotate(" ", xy=v, xytext=[0, 0], color=color,
                arrowprops=dict(facecolor=color,
                                shrink=0,
                                alpha=0.7,
                                width=0.5))
    ax.text(1.1 * v[0], 1.1 * v[1], v)

ax = move_spines()
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid()
vect_fig([2, 4], "blue")
```

## Combinaciones lineales

Cuando trabajamos con [vectores](http://es.wikipedia.org/wiki/Vector), nos vamos a encontrar con dos operaciones fundamentales, la *suma* o *adición*; y la *multiplicación por escalares*. Cuando *sumamos* dos vectores $v$ y $w$, sumamos elemento por elemento, del siguiente modo:

$$v + w
=
\left[
\begin{array}{c}
    v_1 \\
    v_2 \\
    \vdots \\
    v_n
\end{array}
\right]
+
\left[
\begin{array}{c}
     w_1 \\
     w_2 \\
    \vdots \\
     w_n
\end{array}
\right] =
\left[
\begin{array}{c}
    v_1 + w_1 \\
    v_2 + w_2 \\
    \vdots \\
    v_n + w_n
\end{array}
\right]$$

Geométricamente lo podemos ver representado del siguiente modo:

```python
# <!-- collapse=True -->
# graficando suma de vectores en R^2
# [2, 4] + [2, -2]

ax = move_spines()
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid()
vecs = [[2, 4], [2, -2]] # lista de vectores
for v in vecs:
    vect_fig(v, "blue")

v = np.array([2, 4]) + np.array([2, -2])
vect_fig(v, "red")

ax.plot([2, 4], [-2, 2], linestyle='--')
a =ax.plot([2, 4], [4, 2], linestyle='--' )
```

Cuando *multiplicamos [vectores](http://es.wikipedia.org/wiki/Vector) por escalares*, lo que hacemos es tomar un número $\alpha$ y un [vector](http://es.wikipedia.org/wiki/Vector) $v$; y creamos un nuevo [vector](http://es.wikipedia.org/wiki/Vector) $w$ en el cada elemento de $v$ es *multiplicado* por $\alpha$ del siguiente modo:

$$\begin{split}\alpha v
=
\left[
\begin{array}{c}
    \alpha v_1 \\
    \alpha v_2 \\
    \vdots \\
    \alpha v_n
\end{array}
\right]\end{split}$$

Geométricamente podemos representar a esta operación en el plano de 2 dimensiones del siguiente modo:

```python
# <!-- collapse=True -->
# graficando multiplicación por escalares en R^2
# [2, 3] * 2

ax = move_spines()
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.grid()

v = np.array([2, 3])
vect_fig(v, "blue")

v = v * 2
vect_fig(v, "red")
```

Cuando combinamos estas dos operaciones, formamos lo que se conoce en [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) como [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal). Es decir que una [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) va a ser una expresión matemática construida sobre un conjunto de [vectores](http://es.wikipedia.org/wiki/Vector), en el que cada vector es *multiplicado por un escalar* y los resultados son luego *sumados*. Matemáticamente lo podemos expresar de la siguiente forma:

$$w = \alpha_1 v_1 + \alpha_2 v_2 + \dots + \alpha_n v_n = \sum_{i=1}^n \alpha_i v_i
$$

en donde, $v_n$ son [vectores](http://es.wikipedia.org/wiki/Vector) y $\alpha_n$ son escalares.

## Matrices, combinaciones lineales y Ax = b

Una matriz es un arreglo bidimensional de números ordenados en filas y columnas, donde una fila es cada una de las líneas horizontales de la matriz y una columna es cada una de las líneas verticales. En una matriz cada elemento puede ser identificado utilizando dos índices, uno para la fila y otro para la columna en que se encuentra. Las podemos representar de la siguiente manera:

$$A=\begin{bmatrix}a_{11} & a_{12} & \dots & a_{1n}\\a_{21} & a_{22} & \dots & a_{2n}
\\ \vdots & \vdots & \ddots & \vdots \\
a_{n1} & a_{n2} & \dots & a_{nn}\end{bmatrix}$$

Las matrices se utilizan para múltiples aplicaciones y sirven, en particular, para representar los coeficientes de los [sistemas de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) o para representar [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal).

Supongamos que tenemos los siguientes 3 vectores:

$$x_1
=
\left[
\begin{array}{c}
     1 \\
    -1 \\
     0
\end{array}
\right]

x_2 =
\left[
\begin{array}{c}
     0 \\
     1 \\
    -1
\end{array}
\right] \
x_3 =
\left[
\begin{array}{c}
    0 \\
    0 \\
    1
\end{array}
\right]$$

su [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) en el espacio de 3 dimensiones va a ser igual a $\alpha_1 x_1 + \alpha_2 x_2 + \alpha_3 x_3$; lo que es lo mismo que decir:

$$\alpha_1
\left[
\begin{array}{c}
     1 \\
    -1 \\
     0
\end{array}
\right]
+ \alpha_2
\left[
\begin{array}{c}
     0 \\
     1 \\
    -1
\end{array}
\right] + \alpha_3
\left[
\begin{array}{c}
    0 \\
    0 \\
    1
\end{array}
\right] = \left[
\begin{array}{c}
    \alpha_1 \\
    \alpha_2 - \alpha_1 \\
    \alpha_3 - \alpha_2
\end{array}
\right]$$

Ahora esta [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) la podríamos reescribir en forma matricial. Los vectores $x_1, x_2$ y $x_3$, pasarían a formar las columnas de la matriz $A$ y los escalares $\alpha_1, \alpha_2$ y $\alpha_3$ pasarían a ser los componentes del [vector](http://es.wikipedia.org/wiki/Vector) $x$ del siguiente modo:

$$\begin{bmatrix}1 & 0 & 0\\-1 & 1 & 0
\\ 0 & -1 & 1\end{bmatrix}\begin{bmatrix} \alpha_1 \\ \alpha_2 \\ \alpha_3\end{bmatrix}=
\begin{bmatrix}\alpha_1 \\ \alpha_2 - \alpha_1 \\ \alpha_3 - \alpha_2 \end{bmatrix}$$

De esta forma la matriz $A$ multiplicada por el [vector](http://es.wikipedia.org/wiki/Vector) $x$, nos da como resultado la misma [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) $b$. De esta forma, arribamos a una de las ecuaciones más fundamentales del [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html):

$$Ax = b$$

Esta ecuación no solo nos va a servir para expresar [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal), sino que también se vuelve de suma importancia a la hora de resolver [sistemas de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), en dónde $b$ va a ser conocido y la incógnita pasa a ser $x$. Por ejemplo, supongamos que queremos resolver el siguiente [sistemas de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) de 3 incógnitas:

$$ 2x_1 + 3x_2 + 5x_3 = 52 \\
3x_1 + 6x_2 + 2x_3 = 61 \\
8x_1 + 3x_2 + 6x_3 = 75
$$

Podemos ayudarnos de [SymPy](http://www.sympy.org/es/) para expresar a la matriz $A$ y $b$ para luego arribar a la solución del [vector](http://es.wikipedia.org/wiki/Vector) $x$.

```python
# Resolviendo sistema de ecuaciones con SymPy
A = sympy.Matrix(( (2, 3, 5), (3, 6, 2), (8, 3, 6) ))
A
```

$\displaystyle \left[\begin{matrix}2 & 3 & 5\\3 & 6 & 2\\8 & 3 & 6\end{matrix}\right]$

```python
b = sympy.Matrix(3,1,(52,61,75))
b
```

$\displaystyle \left[\begin{matrix}52\\61\\75\end{matrix}\right]$

```python
# Resolviendo Ax = b
x = A.LUsolve(b)
x
```

$\displaystyle \left[\begin{matrix}3\\7\\5\end{matrix}\right]$

```python
# Comprobando la solución
A*x
```

$\displaystyle \left[\begin{matrix}52\\61\\75\end{matrix}\right]$

## La matriz  identidad , la matriz transpuesta y la matriz invertible

Tres matrices de suma importancia en problemas de [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html). Son la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad), la [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta) y la [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible).

La [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es el elemento neutro en la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), es el equivalente al número 1. Cualquier matriz multiplicada por la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) nos da como resultado la misma matriz. La [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) (tiene siempre el mismo número de filas que de columnas); y su diagonal principal se compone de todos elementos 1 y el resto de los elementos se completan con 0. Suele representase con la letra $I$.

Por ejemplo la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) de 3x3 sería la siguiente:

$$I=\begin{bmatrix}1 & 0 & 0 & \\0 & 1 & 0\\ 0 & 0 & 1\end{bmatrix}$$

La [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta) de una matriz $A$ de $m \times n$ va a ser igual a la matriz $n \times m$ $A^T$, la cual se obtiene al transformar las filas en columnas y las columnas en filas, del siguiente modo:

$$\begin{bmatrix}a & b & \\c & d & \\ e & f & \end{bmatrix}^T=
\begin{bmatrix}a & c & e &\\b & d & f & \end{bmatrix}$$

Una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) va a ser *[simétrica](https://es.wikipedia.org/wiki/Matriz_sim%C3%A9trica)* si $A^T = A$, es decir si $A$ es igual a su propia [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta).

Algunas de las propiedades de las [matrices transpuestas](http://es.wikipedia.org/wiki/Matriz_transpuesta) son:

a. $(A^T)^T = A$

b. $(A + B)^T = A^T + B^T$

c. $k(A)^T = k(A^T)$

d. $(AB)^T = B^T A^T$

e. $(A^r)^T = (A^T)^r$ para todos los $r$ no negativos.

f. Si $A$ es una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada), entonces $A + A^T$ es una [matriz simétrica](https://es.wikipedia.org/wiki/Matriz_sim%C3%A9trica).

g. Para cualquier matriz $A$, $A A^T$ y $A^T A$ son [matrices simétricas](https://es.wikipedia.org/wiki/Matriz_sim%C3%A9trica).

Veamos algunos ejemplos en [Python](http://python.org/)

```python
# Matriz transpuesta
A = sympy.Matrix( [[ 2,-3,-8, 7],
                   [-2,-1, 2,-7],
                   [ 1, 0,-3, 6]] )
A
```

$\displaystyle \left[\begin{matrix}2 & -3 & -8 & 7\\-2 & -1 & 2 & -7\\1 & 0 & -3 & 6\end{matrix}\right]$

```python
A.transpose()
```

$\displaystyle \left[\begin{matrix}2 & -2 & 1\\-3 & -1 & 0\\-8 & 2 & -3\\7 & -7 & 6\end{matrix}\right]$

```python
# transpuesta de transpuesta vuelve a A.
A.transpose().transpose()
```

$\displaystyle \left[\begin{matrix}2 & -3 & -8 & 7\\-2 & -1 & 2 & -7\\1 & 0 & -3 & 6\end{matrix}\right]$

```python
# creando matriz simetrica
As = A*A.transpose()
As
```

$\displaystyle \left[\begin{matrix}126 & -66 & 68\\-66 & 58 & -50\\68 & -50 & 46\end{matrix}\right]$

```python
# comprobando simetria.
As.transpose()
```

$\displaystyle \left[\begin{matrix}126 & -66 & 68\\-66 & 58 & -50\\68 & -50 & 46\end{matrix}\right]$

La [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible) es muy importante, ya que esta relacionada con la ecuación $Ax = b$. Si tenemos una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) $A$ de $n \times n$, entonces la [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) de $A$ es una matriz $A'$ o $A^{-1}$ de $n \times n$ que hace que la multiplicación $A A^{-1}$ sea igual a la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) $I$. Es decir que es la matriz recíproca de $A$.

$A A^{-1} = I$ o $A^{-1} A = I$

En caso de que estas condiciones se cumplan, decimos que la [matriz es invertible](https://es.wikipedia.org/wiki/Matriz_invertible).

Que una matriz sea [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) tiene importantes implicaciones, como ser:

a. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces su [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) es única.

b. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible) de $n \times n$, entonces el [sistemas de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) dado por $Ax = b$ tiene una única solución $x = A^{-1}b$ para cualquier $b$ en $\mathbb{R}^n$.

c. Una matriz va a ser [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) si y solo si su determinante es distinto de cero. En el caso de que el determinante sea cero se dice que la matriz es singular.

d. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces el [sistema](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) $Ax = 0$ solo tiene una solución *trivial*. Es decir, en las que todas las incógnitas son ceros.

e. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces su [forma escalonada](https://es.wikipedia.org/wiki/Matriz_escalonada) va a ser igual a la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad).

f. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces $A^{-1}$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(A^{-1})^{-1} = A$$

g. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y $\alpha$ es un  escalar distinto de cero, entonces $\alpha A$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(\alpha A)^{-1} = \frac{1}{\alpha}A^{-1}$$

h. Si $A$ y $B$ son [matrices invertibles](https://es.wikipedia.org/wiki/Matriz_invertible) del mismo tamaño, entonces $AB$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(AB)^{-1} = B^{-1} A^{-1}$$

i. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces $A^T$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(A^T)^{-1} = (A^{-1})^T$$

Con [SymPy](http://www.sympy.org/es/) podemos trabajar con las [matrices invertibles](https://es.wikipedia.org/wiki/Matriz_invertible) del siguiente modo:

```python
# Matriz invertible
A = sympy.Matrix( [[1,2],
                   [3,9]] )
A
```

$\displaystyle \left[\begin{matrix}1 & 2\\3 & 9\end{matrix}\right]$

```python
A_inv = A.inv()
A_inv
```

$\displaystyle \left[\begin{matrix}3 & - \frac{2}{3}\\-1 & \frac{1}{3}\end{matrix}\right]$

```python
# A * A_inv = I
A*A_inv
```

$\displaystyle \left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right]$

```python
# forma escalonada igual a indentidad.
A.rref()
```

$\displaystyle \left( \left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right], \  \left( 0, \  1\right)\right)$

```python
# la inversa de A_inv es A
A_inv.inv()
```

$\displaystyle \left[\begin{matrix}1 & 2\\3 & 9\end{matrix}\right]$

## Espacios vectoriales

Las Matemáticas derivan su poder en gran medida de su capacidad para encontrar las características comunes de los diversos problemas y estudiarlos de manera abstracta. Existen muchos problemas que implican los conceptos relacionados de *adición*, *multiplicación por escalares*, y la [linealidad](https://es.wikipedia.org/wiki/Lineal). Para estudiar estas propiedades de manera abstracta, debemos  introducir la noción de [espacio vectorial](https://es.wikipedia.org/wiki/Espacio_vectorial).

Para alcanzar la definición de un [espacio vectorial](https://es.wikipedia.org/wiki/Espacio_vectorial), debemos combinar los conceptos que venimos viendo hasta ahora de Campo, [vector](http://es.wikipedia.org/wiki/Vector) y las operaciones de *adición*; y *multiplicación por escalares*. De esta forma un [espacio vectorial](https://es.wikipedia.org/wiki/Espacio_vectorial), $V$, sobre un Campo, $F$,  va a ser un [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) en el que están definidas las operaciones de *adición* y  *multiplicación por escalares*, tal que para cualquier par de elementos $x$ e $y$ en $V$, existe un elemento único $x + y$ en $V$, y para cada elemento $\alpha$ en $F$ y cada elemento $x$ en $V$, exista un único elemento $\alpha x$ en $V$, de manera que se cumplan las siguientes condiciones:

1. Para todo $x, y$ en $V$, $x + y = y + x$ ([conmutatividad](https://es.wikipedia.org/wiki/Conmutatividad) de la adición).

2. Para todo $x, y, z$ en $V$, $(x + y) + z = x + (y + z)$. (asociatividad de la adición).

3. Existe un elemento en $V$ llamado $0$ tal que $x + 0 = x$ para todo $x$ en $V$.

4. Para cada elemento $x$ en $V$, existe un elemento $y$ en $V$ tal que $x + y = 0$.

5. Para cada elemento $x$ en $V$, $1 x = x$.

6. Para cada par, $\alpha, \beta$ en $F$ y cada elemento $x$ en $V$, $(\alpha \beta) x = \alpha (\beta x)$.

7. Para cada elemento $\alpha$ en $F$ y cada para de elementos $x, y$ en $V$, $\alpha(x + y) = \alpha x + \alpha y$.

8. Para cada par de elementos $\alpha, \beta$ en $F$ y cada elemento $x$ en $V$, $(\alpha +  \beta)x = \alpha x + \beta x$.

Los [espacios vectoriales](https://es.wikipedia.org/wiki/Espacio_vectorial) más comunes son $\mathbb{R}^2$, el cual representa el plano de 2 dimensiones y consiste de todos los pares ordenados de los [números reales](https://es.wikipedia.org/wiki/N%C3%BAmero_real):

$$\mathbb{R}^2 = \{(x, y): x, y \in \mathbb{R}\}$$

y $\mathbb{R}^3$, que representa el espacio ordinario de 3 dimensiones y consiste en todos los tríos ordenados de los  [números reales](https://es.wikipedia.org/wiki/N%C3%BAmero_real):

$$\mathbb{R}^3 = \{(x, y, z): x, y, z \in \mathbb{R}\}$$

Una de las grandes bellezas del [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) es que podemos fácilmente pasar a trabajar sobre espacios de $n$ dimensiones, $\mathbb{R}^n$!

Tampoco tenemos porque quedarnos con solo los [números reales](https://es.wikipedia.org/wiki/N%C3%BAmero_real), ya que la definición que dimos de un [espacio vectorial](https://es.wikipedia.org/wiki/Espacio_vectorial) reside sobre un Campo; y los campos pueden estar representados por [números complejos](http://relopezbriega.github.io/blog/2015/10/12/numeros-complejos-con-python/). Por tanto también podemos tener [espacios vectoriales](https://es.wikipedia.org/wiki/Espacio_vectorial)  $\mathbb{C}^2, \mathbb{C}^3, \dots, \mathbb{C}^n$.

### Subespacios

Normalmente, en el estudio de cualquier estructura algebraica es interesante examinar subconjuntos que tengan la misma estructura que el [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) que esta siendo considerado. Así, dentro de los [espacios vectoriales](https://es.wikipedia.org/wiki/Espacio_vectorial),  podemos tener [subespacios vectoriales](https://es.wikipedia.org/wiki/Subespacio_vectorial), los cuales son un subconjunto que cumplen con las mismas *propiedades* que el [espacio vectorial](https://es.wikipedia.org/wiki/Espacio_vectorial) que los contiene. De esta forma, $\mathbb{R}^3$ representa un [subespacio](https://es.wikipedia.org/wiki/Subespacio_vectorial) del [espacio vectorial](https://es.wikipedia.org/wiki/Espacio_vectorial) $\mathbb{R}^n$.

## Independencia lineal

La [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) es un concepto aparentemente simple con consecuencias que se extienden profundamente en muchos aspectos del análisis. Si deseamos entender cuándo una matriz puede ser [invertible](https://es.wikipedia.org/wiki/Matriz_invertible), o cuándo un [sistema de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) tiene una única solución, o cuándo una estimación por [mínimos cuadrados](https://es.wikipedia.org/wiki/M%C3%ADnimos_cuadrados) se define de forma única, la idea fundamental más importante es la de [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) de [vectores](http://es.wikipedia.org/wiki/Vector).

Dado un conjunto finito de [vectores](http://es.wikipedia.org/wiki/Vector) $x_1, x_2, \dots, x_n$ se dice que los mismos son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*, si y solo si, los únicos escalares $\alpha_1, \alpha_2, \dots, \alpha_n$ que satisfacen la ecuación:

$$\alpha_1 x_1 + \alpha_2 x_2 + \dots + \alpha_n x_n = 0$$

son todos ceros, $\alpha_1 = \alpha_2 = \dots = \alpha_n = 0$.

En caso de que esto no se cumpla, es decir, que existe una solución a la ecuación de arriba en que no todos los escalares son ceros, a esta solución se la llama *no trivial* y se dice que los [vectores](http://es.wikipedia.org/wiki/Vector) son *[linealmente dependientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*.

Para ilustrar la definición y que quede más clara, veamos algunos ejemplos. Supongamos que queremos determinar si los siguientes [vectores](http://es.wikipedia.org/wiki/Vector) son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*:

$$\begin{split}x_1
=
\left[
\begin{array}{c}
    1.2 \\
1.1 \\
\end{array}
\right] \  \  \ x_2 =
\left[
\begin{array}{c}
    -2.2 \\
1.4 \\
\end{array}
\right]\end{split}$$

Para lograr esto, deberíamos resolver el siguiente [sistema de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) y verificar si la única solución es aquella en que los escalares sean ceros.

$$\begin{split}\alpha_1
\left[
\begin{array}{c}
    1.2 \\
1.1 \\
\end{array}
\right] + \alpha_2
\left[
\begin{array}{c}
    -2.2 \\
1.4 \\
\end{array}
\right]\end{split} = 0
$$

Para resolver este [sistema de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), podemos recurrir a la ayuda de [Python](http://python.org/).

```python
# Resolviendo el sistema de ecuaciones.
A = np.array([[1.2, -2.2],
              [1.1, 1.4]])

b = np.array([0., 0.])

x = np.linalg.solve(A, b)
x
```

    array([0., 0.])

```python
# <!-- collapse=True -->
# Solución gráfica.
x_vals = np.linspace(-5, 5, 50) # crea 50 valores entre 0 y 5

ax = move_spines()
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid()

ax.plot(x_vals, (1.2 * x_vals) / -2.2) # grafica 1.2x_1 - 2.2x_2 = 0
a = ax.plot(x_vals, (1.1 * x_vals) / 1.4) # grafica 1.1x + 1.4x_2 = 0
```

Como podemos ver, tanto por la solución numérica como por la solución gráfica, estos vectores son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*, ya que la única solución a la ecuación $\alpha_1 x_1 + \alpha_2 x_2 + \dots + \alpha_n x_n = 0$, es aquella en que los escalares son cero.

Determinemos ahora si por ejemplo, los siguientes [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^4$ son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*: $\{(3, 2, 2, 3), (3, 2, 1, 2), (3, 2, 0, 1)\}$. Aquí, ahora deberíamos resolver la siguiente ecuación:

$$\alpha_1 (3, 2, 2, 3) +\alpha_2 (3, 2, 1, 2) + \alpha_3 (3, 2, 0, 1) = (0, 0, 0, 0)$$

Para resolver este sistema de ecuaciones que no es cuadrado (tiene 4 ecuaciones y solo 3 incógnitas); podemos utilizar [SymPy](http://www.sympy.org/es/).

```python
# Sympy para resolver el sistema de ecuaciones lineales
a1, a2, a3 = sympy.symbols('a1, a2, a3')
A = sympy.Matrix(( (3, 3, 3, 0), (2, 2, 2, 0), (2, 1, 0, 0), (3, 2, 1, 0) ))
A
```

$\displaystyle \left[\begin{matrix}3 & 3 & 3 & 0\\2 & 2 & 2 & 0\\2 & 1 & 0 & 0\\3 & 2 & 1 & 0\end{matrix}\right]$

```python
sympy.solve_linear_system(A, a1, a2, a3)
```

$\displaystyle \left\{ a_{1} : a_{3}, \  a_{2} : - 2 a_{3}\right\}$

Como vemos, esta solución es *no trivial*, ya que por ejemplo existe la solución $\alpha_1 = 1, \ \alpha_2 = -2 , \  \alpha_3 = 1$ en la que los escalares no son ceros. Por lo tanto este sistema es *[linealmente dependiente](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*.

Por último, podríamos considerar si los siguientes [polinomios](https://es.wikipedia.org/wiki/Polinomio) son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*: $1 -2x -x^2$, $1 + x$, $1 + x + 2x^2$. En este caso, deberíamos resolver la siguiente ecuación:

$$\alpha_1 (1 − 2x − x^2) + \alpha_2 (1 + x) + \alpha_3 (1 + x + 2x^2) = 0$$

y esta ecuación es equivalente a la siguiente:

$$(\alpha_1 + \alpha_2 + \alpha_3 ) + (−2 \alpha_1 + \alpha_2 + \alpha_3 )x + (−\alpha_1 + 2 \alpha_2 )x^2 = 0$$

Por lo tanto, podemos armar el siguiente [sistema de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales):

$$\alpha_1 + \alpha_2 + \alpha_3 = 0, \\
-2 \alpha_1 + \alpha_2 + \alpha_3 = 0, \\
-\alpha_1 + 2 \alpha_2  = 0.
$$

El cual podemos nuevamente resolver con la ayuda de [SymPy](http://www.sympy.org/es/).

```python
A = sympy.Matrix(( (1, 1, 1, 0), (-2, 1, 1, 0), (-1, 2, 0, 0) ))
A
```

$\displaystyle \left[\begin{matrix}1 & 1 & 1 & 0\\-2 & 1 & 1 & 0\\-1 & 2 & 0 & 0\end{matrix}\right]$

```python
sympy.solve_linear_system(A, a1, a2, a3)
```

$\displaystyle \left\{ a_{1} : 0, \  a_{2} : 0, \  a_{3} : 0\right\}$

Como vemos, todos los escalares son ceros, por lo tanto estos [polinomios](https://es.wikipedia.org/wiki/Polinomio) son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*.

### Espacio nulo, espacio columna y espacio fila

Un termino particularmente relacionado con la [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) es el de espacio nulo o núcleo. El espacio nulo de una matriz $A$, el cual lo vamos a expresar como $N(A)$, va a consistir de todas las soluciones a la ecuación fundamental $Ax = 0$. Por supuesto, una solución inmediata a esta ecuación es el caso de $x = 0$, que ya vimos que establece la [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal). Esta solución solo va a ser la única que exista para los casos de [matrices invertibles](https://es.wikipedia.org/wiki/Matriz_invertible). Pero en el caso de las matrices singulares (aquellas que no son [invertibles](https://es.wikipedia.org/wiki/Matriz_invertible), que tienen determinante igual a cero), van a existir soluciones que no son cero para la ecuación $Ax = 0$. El conjunto de todas estas soluciones, va a representar el espacio nulo.

Para encontrar el espacio nulo también nos podemos ayudar de [SymPy](http://www.sympy.org/es/).

```python
# Espacio nulo de un matriz
A = sympy.Matrix(((1, 5, 7), (0, 0, 9)))
A
```

$\displaystyle \left[\begin{matrix}1 & 5 & 7\\0 & 0 & 9\end{matrix}\right]$

```python
# Calculando el espacio nulo
x = A.nullspace()
x
```

$\displaystyle \left[ \left[\begin{matrix}-5\\1\\0\end{matrix}\right]\right]$

```python
# Comprobando la solución
A_aum = sympy.Matrix(((1, 5, 7, 0), (0, 0, 9, 0)))
sympy.solve_linear_system(A_aum, a1, a2, a3)
```

$\displaystyle \left\{ a_{1} : - 5 a_{2}, \  a_{3} : 0\right\}$

```python
# Comprobación con numpy
A = np.array([[1, 5, 7],
               [0, 0, 9]])
x = np.array([[-5],
               [1],
               [0]])

A.dot(x)
```

    array([[0],
           [0]])

Otro espacio de suma importancia es el [espacio columna](https://es.wikipedia.org/wiki/Subespacios_fundamentales_de_una_matriz). El [espacio columna](https://es.wikipedia.org/wiki/Subespacios_fundamentales_de_una_matriz), $C(A)$, consiste en todas las [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) de las columnas de una matriz $A$. Estas combinaciones son los posibles vectores $Ax$. Este espacio es fundamental para resolver la ecuación $Ax = b$; ya que para resolver esta ecuación debemos expresar a $b$ como una combinación de columnas. El sistema $Ax = b$, va a tener solución solamente si $b$ esta en el espacio columna de $A$. Como las matrices tienen la forma $m \times n$, sus columnas tienen $m$ componentes ($n$ son las filas). Por lo tanto el [espacio columna](https://es.wikipedia.org/wiki/Subespacios_fundamentales_de_una_matriz) es un *subespacio* de $\mathbb{R}^m$ y no $\mathbb{R}^n$.

Por último, el otro espacio que conforma los [espacios fundamentales](https://es.wikipedia.org/wiki/Subespacios_fundamentales_de_una_matriz) de una matriz, es el [espacio fila](https://es.wikipedia.org/wiki/Subespacios_fundamentales_de_una_matriz), el cual esta constituido por las [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) de las filas de una matriz.

Para obtener estos espacios, nuevamente podemos recurrir a [SymPy](http://www.sympy.org/es/). Para poder obtener estos espacios, primero vamos a tener que obtener la [forma escalonada](https://es.wikipedia.org/wiki/Matriz_escalonada) de la matriz, la cual es la forma a la que arribamos luego del proceso de [eliminación](https://es.wikipedia.org/wiki/Eliminaci%C3%B3n_de_Gauss-Jordan).

```python
# A.rref() forma escalonada.
A = sympy.Matrix( [[2,-3,-8, 7],
                   [-2,-1,2,-7],
                   [1 ,0,-3, 6]])

A.rref() # [0, 1, 2] es la ubicación de las pivot.
```

$\displaystyle \left( \left[\begin{matrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 3\\0 & 0 & 1 & -2\end{matrix}\right], \  \left( 0, \  1, \  2\right)\right)$

```python
# Espacio columna
[ A[:,c] for c in A.rref()[1] ]
```

$\displaystyle \left[ \left[\begin{matrix}2\\-2\\1\end{matrix}\right], \  \left[\begin{matrix}-3\\-1\\0\end{matrix}\right], \  \left[\begin{matrix}-8\\2\\-3\end{matrix}\right]\right]$

```python
# Espacio fila
[ A.rref()[0][r,:] for r in A.rref()[1] ]
```

$\displaystyle \left[ \left[\begin{matrix}1 & 0 & 0 & 0\end{matrix}\right], \  \left[\begin{matrix}0 & 1 & 0 & 3\end{matrix}\right], \  \left[\begin{matrix}0 & 0 & 1 & -2\end{matrix}\right]\right]$

## Rango

Otro concepto que también esta ligado a la [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) es el de rango. Los números de columnas $m$ y filas $n$ pueden darnos el tamaño de una matriz, pero esto no necesariamente representa el verdadero tamaño del [sistema lineal](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), ya que por ejemplo si existen dos filas iguales en una matriz $A$, la segunda fila desaparecía en el proceso de [eliminación](https://es.wikipedia.org/wiki/Eliminaci%C3%B3n_de_Gauss-Jordan). El verdadero tamaño de $A$ va a estar dado por su rango. El rango de una matriz es el número máximo de columnas (filas respectivamente) que son [linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal). Por ejemplo si tenemos la siguiente matriz de 3 x 4:

$$A = \begin{bmatrix}1 & 1 & 2 & 4\\1 & 2 & 2 & 5
\\ 1 & 3 & 2 & 6\end{bmatrix}$$

Podemos ver que la tercer columna $(2, 2, 2)$ es un múltiplo de la primera y que la cuarta columna $(4, 5, 6)$ es la suma de las primeras 3 columnas. Por tanto el rango de $A$ va a ser igual a 2; ya que la tercer y cuarta columna pueden ser eliminadas.

Obviamente, el rango también lo podemos calcular con la ayuda de [Python](http://python.org/).

```python
# Calculando el rango con SymPy
A = sympy.Matrix([[1, 1, 2, 4],
                  [1, 2, 2, 5],
                  [1, 3, 2, 6]])
A
```

$\displaystyle \left[\begin{matrix}1 & 1 & 2 & 4\\1 & 2 & 2 & 5\\1 & 3 & 2 & 6\end{matrix}\right]$

```python
# Rango con SymPy
A.rank()
```

$\displaystyle 2$

```python
# Rango con numpy
A = np.array([[1, 1, 2, 4],
              [1, 2, 2, 5],
              [1, 3, 2, 6]])
np.linalg.matrix_rank(A)
```

    2

Una útil aplicación de calcular el rango de una matriz es la de determinar el número de soluciones al [sistema de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), de acuerdo al enunciado del [Teorema de Rouché–Frobenius](https://es.wikipedia.org/wiki/Teorema_de_Rouch%C3%A9%E2%80%93Frobenius). El sistema tiene por lo menos una solución si el rango de la matriz de coeficientes equivale al rango de la [matriz aumentada](https://es.wikipedia.org/wiki/Matriz_aumentada). En ese caso, ésta tiene exactamente una solución si el rango equivale al número de incógnitas.

## La norma y la Ortogonalidad

Si quisiéramos saber cual es el *largo* del un [vector](http://es.wikipedia.org/wiki/Vector), lo único que necesitamos es el famoso [teorema de Pitágoras](https://es.wikipedia.org/wiki/Teorema_de_Pit%C3%A1goras). En el plano $\mathbb{R}^2$, el *largo* de un [vector](http://es.wikipedia.org/wiki/Vector) $v=\begin{bmatrix}a \\ b \end{bmatrix}$ va a ser igual a la distancia desde el origen $(0, 0)$ hasta el punto $(a, b)$. Esta distancia puede ser fácilmente calculada gracias al [teorema de Pitágoras](https://es.wikipedia.org/wiki/Teorema_de_Pit%C3%A1goras) y va ser igual a $\sqrt{a^2 + b^2}$, como se puede ver en la siguiente figura:

```python
# <!-- collapse=True -->
# Calculando largo de un vector
# forma un triángulo rectángulo

ax = move_spines()
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.grid()
v = np.array([4, 6])

vect_fig(v, "blue")

a = ax.vlines(x=v[0], ymin=0, ymax = 6, linestyle='--', color='g')
```

En esta definición podemos observar que $a^2 + b^2 = v \cdot v$, por lo que ya estamos en condiciones de poder definir lo que en [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) se conoce como [norma](https://es.wikipedia.org/wiki/Norma_vectorial).

El *largo* o [norma](https://es.wikipedia.org/wiki/Norma_vectorial) de un [vector](http://es.wikipedia.org/wiki/Vector) $v = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix}$, en $\mathbb{R}^n$ va a ser igual a un número no negativo $||v||$ definido por:

$$||v|| = \sqrt{v \cdot v} = \sqrt{v_1^2 + v_2^2 + \dots + v_n^2}$$

Es decir que la [norma](https://es.wikipedia.org/wiki/Norma_vectorial) de un [vector](http://es.wikipedia.org/wiki/Vector) va a ser igual a la raíz cuadrada de la suma de los cuadrados de sus componentes.

### Ortogonalidad

El concepto de [perpendicularidad](https://es.wikipedia.org/wiki/Perpendicularidad) es fundamental en [geometría](https://es.wikipedia.org/wiki/Geometr%C3%ADa). Este concepto llevado a los [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^n$ se llama ortogonalidad.

Dos [vectores](http://es.wikipedia.org/wiki/Vector) $v$ y $w$ en $\mathbb{R}^n$ van a ser ortogonales el uno al otro si su [producto interior](https://es.wikipedia.org/wiki/Producto_escalar) es igual a cero. Es decir, $v \cdot w = 0$.

Geométricamente lo podemos ver de la siguiente manera:

```python
# <!-- collapse=True -->
# Vectores ortogonales

ax = move_spines()
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.grid()
vecs = [np.array([4, 6]), np.array([-3, 2])]

for v in vecs:
    vect_fig(v, "blue")

a = ax.plot([-3, 4], [2, 6], linestyle='--', color='g')
```

```python
# comprobando su producto interior.
v = np.array([4, 6])
w = np.array([-3, 2])
v.dot(w)
```

    0

Un [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^n$ va a ser ortogonal si todo los pares de los distintos [vectores](http://es.wikipedia.org/wiki/Vector) en el [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) son ortogonales entre sí. O sea:

$v_i \cdot v_j = 0$ para todo $i, j = 1, 2, \dots, k$ y donde $i \ne j$.

Por ejemplo, si tenemos el siguiente [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^3$:

$$v1=\begin{bmatrix} 2 \\ 1 \\ -1\end{bmatrix} \
v2=\begin{bmatrix} 0 \\ 1 \\ 1\end{bmatrix}
v3=\begin{bmatrix} 1 \\ -1 \\ 1\end{bmatrix}$$

En este caso, deberíamos combrobar que:

$$v1 \cdot v2 = 0 \\
v2 \cdot v3 = 0 \\
v1 \cdot v3 = 0 $$

```python
# comprobando ortogonalidad del conjunto

v1 = np.array([2, 1, -1])
v2 = np.array([0, 1, 1])
v3 = np.array([1, -1, 1])

v1.dot(v2), v2.dot(v3), v1.dot(v3)
```

    (0, 0, 0)

Como vemos, este conjunto es ortogonal. Una de las principales ventajas de trabajar con [conjuntos](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de [vectores](http://es.wikipedia.org/wiki/Vector) ortogonales es que los mismos son necesariamente [linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal).

El concepto de ortogonalidad es uno de los más importantes y útiles en [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) y surge en muchas situaciones prácticas, sobre todo cuando queremos calcular distancias.

## Determinante

El determinante es un número especial que puede calcularse sobre las [matrices cuadradas](https://es.wikipedia.org/wiki/Matriz_cuadrada). Este número nos va a decir muchas cosas sobre la matriz. Por ejemplo, nos va decir si la matriz es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) o no. Si el determinante es igual a cero, la matriz no es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible). Cuando la matriz es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible), el determinante de $A^{-1}= 1/(\det \ A)$. El determinante también puede ser útil para calcular áreas.

Para obtener el determinante de una matriz debemos calcular la suma de los productos de las diagonales de la matriz en una dirección menos la suma de los productos de las diagonales en la otra dirección. Se represente con el símbolo $|A|$ o $\det A$.

Algunas de sus propiedades que debemos tener en cuenta son:

a. El determinante de la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es igual a 1. $\det I = 1$.

b. Una matriz $A$ es *singular* (no tiene [inversa](https://es.wikipedia.org/wiki/Matriz_invertible)) si su determinante es igual a cero.

c. El determinante cambia de signo cuando dos columnas(o filas) son intercambiadas.

d. Si dos filas de una matriz $A$ son iguales, entonces el determinante es cero.

e. Si alguna fila de la matriz $A$ son todos ceros, entonces el determinante es cero.

f. La [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta) $A^T$, tiene el mismo determinante que $A$.

g. El determinante de $AB$ es igual al determinante de $A$ multiplicado por el determinante de $B$. $\det (AB) = \det A \cdot \det B$.

h. El determinante es una [función lineal](https://es.wikipedia.org/wiki/Funci%C3%B3n_lineal) de cada una de las filas en forma separada. Si multiplicamos solo una fila por $\alpha$, entonces el determinante también es multiplicado por $\alpha$.

Veamos como podemos obtener el determinante con la ayuda de [Python](http://python.org/)

```python
# Determinante con sympy
A = sympy.Matrix( [[1, 2, 3],
                   [2,-2, 4],
                   [2, 2, 5]] )
A.det()
```

$\displaystyle 2$

```python
# Determinante con numpy
A = np.array([[1, 2, 3],
              [2,-2, 4],
              [2, 2, 5]] )
np.linalg.det(A)
```

$\displaystyle 2.0$

```python
# Determinante como funcion lineal de fila
A[0] = A[0:1]*5
np.linalg.det(A)
```

$\displaystyle 9.99999999999998$

```python
# cambio de signo de determinante
A = sympy.Matrix( [[2,-2, 4],
                   [1, 2, 3],
                   [2, 2, 5]] )
A.det()
```

$\displaystyle -2$

*Esta notebook fue creada originalmente como un blog post por [Raúl E. López Briega](http://relopezbriega.com.ar/) en [Mi blog sobre Python](http://relopezbriega.github.io). El contenido esta bajo la licencia BSD.*

*Este post fue escrito utilizando IPython notebook. Pueden descargar este [notebook](https://github.com/relopezbriega/relopezbriega.github.io/blob/master/downloads/LinearAlgebraPython.ipynb) o ver su version estática en [nbviewer](http://nbviewer.ipython.org/github/relopezbriega/relopezbriega.github.io/blob/master/downloads/LinearAlgebraPython.ipynb).*

```python

```


# Information Quantities for Decision Tree
In this notebook, we will use the [`dit` package](https://dit.readthedocs.io/en/latest/) to compute some basic information quantities used in the decision tree algorithms. A quick reference of Shannon's information measures are given in the last section.

Install and load required packages.

```
!pip install dit # install the package
```

```
import dit       # load the package
from dit.shannon import entropy, conditional_entropy, mutual_information
from IPython.display import display, Markdown, Math, HTML # To pretty print on screen.
import matplotlib.pyplot as plt
```

The following is needed for rending Mathjax in Colab output cells. (Click [here](https://github.com/googlecolab/colabtools/issues/594) for details.)

```
def typeset():
  """MathJax initialization for the current cell.
  """
  display(HTML('''

      '''))
```

## Entropy of a distribution

Create a distribution:
\begin{align}
p_k=\begin{cases}
\frac12 & k=0\\
\frac14 & k=1,2\\
0 & \text{otherwise.}
\end{cases}
\end{align}

```
p = dit.Distribution(['0', '1', '2', '3'], [1/2, 1/4, 1/4, 0])

plt.stem(p.outcomes,p.pmf,use_line_collection=True)
plt.xlabel('k')
plt.ylabel(r'$p_k$')
plt.ylim((0,1))
plt.show()
```

The entropy of the distribution can be computed as follows:

```
typeset()
display(Math(r'h(p_1,p_2,\dots)={}'.format(entropy(p))))
```

$$h(p_1,p_2,\dots)=1.5$$

## Quality of splitting attributes

Dataset $D$ from the lecture (Slide 19).

|$X_1$|$X_2$|$X_3$|$X_4$|$Y$|
|-----|-----|-----|-----|---|
|0    |0    |0    |00   |0  |
|0    |0    |0    |00   |0  |
|0    |0    |1    |01   |1  |
|1    |0    |1    |11   |1  |
|0    |1    |0    |00   |2  |
|1    |1    |0    |10   |2  |
|1    |1    |1    |11   |3  |
|1    |1    |1    |11   |3  |

Create a uniform distribution over the instances in $D$.

```
d = dit.uniform([('0','0','0','00','0'),
                 ('0','0','0','00','0'),
                 ('0','0','1','01','1'),
                 ('1','0','1','11','1'),
                 ('0','1','0','00','2'),
                 ('1','1','0','10','2'),
                 ('1','1','1','11','3'),
                 ('1','1','1','11','3')])
d.set_rv_names(('X1','X2','X3','X4','Y'))
d
```

Class:	Distribution

Alphabet:	(('0', '1'), ('0', '1'), ('0', '1'), ('00', '01', '10', '11'), ('0', '1', '2', '3'))

Base:	linear

Outcome Class:	tuple

Outcome Lenght:	5

X1	X2	X3	X4	Y	p(x)

0	0	0	00	0	0.125

0	0	0	00	0	0.125

0	0	1	01	1	0.125

0	1	0	00	2	0.125

1	0	1	11	1	0.125

1	1	0	10	2	0.125

1	1	1	11	3	0.125

1	1	1	11	3	0.125

Calculate $\text{Info}(D)$ and $\text{Info}_{X_i}(D)$ for $i=\{1,2,3,4\}$ as the entropy $H(Y)$ and conditional entropies $H(Y|X_i)$'s respectively.

```
InfoD = entropy(d,['Y'])
InfoX1D = conditional_entropy(d,['Y'],['X1'])
InfoX2D = conditional_entropy(d,['Y'],['X2'])
InfoX3D = conditional_entropy(d,['Y'],['X3'])
InfoX4D = conditional_entropy(d,['Y'],['X4'])
```

```
typeset()
Math(r'''
\begin{{aligned}}
\text{{Info}}(D)&={}\\
\text{{Info}}_{{X_1}}(D)&={:.3g}\\
\text{{Info}}_{{X_2}}(D)&={:.3g}\\
\text{{Info}}_{{X_3}}(D)&={:.3g}\\
\text{{Info}}_{{X_4}}(D)&={:.3g}\\
\end{{aligned}}
'''.format(InfoD,InfoX1D,InfoX2D,InfoX3D,InfoX4D))
```

$$
\begin{aligned}
\text{Info}(D)&=2.0\\
\text{Info}_{X_1}(D)&=1.5\\
\text{Info}_{X_2}(D)&=1\\
\text{Info}_{X_3}(D)&=1\\
\text{Info}_{X_4}(D)&=0.689\\
\end{aligned}
$$

Calculate $\text{SplitInfo}_{X_i}(D)$ as $H(X_i)$.

```
SplitInfoX1D = entropy(d,['X1'])
SplitInfoX2D = entropy(d,['X2'])
SplitInfoX3D = entropy(d,['X3'])
SplitInfoX4D = entropy(d,['X4'])
```

```
typeset()
Math(r'''
\begin{{aligned}}
\text{{SplitInfo}}_{{X_1}}(D)&={:.3g}\\
\text{{SplitInfo}}_{{X_2}}(D)&={:.3g}\\
\text{{SplitInfo}}_{{X_3}}(D)&={:.3g}\\
\text{{SplitInfo}}_{{X_4}}(D)&={:.3g}\\
\end{{aligned}}
'''.format(SplitInfoX1D,SplitInfoX2D,SplitInfoX3D,SplitInfoX4D))
```

$$
\begin{aligned}
\text{SplitInfo}_{X_1}(D)&=1\\
\text{SplitInfo}_{X_2}(D)&=1\\
\text{SplitInfo}_{X_3}(D)&=1\\
\text{SplitInfo}_{X_4}(D)&=1.81\\
\end{aligned}
$$

Calculate the information gain $\text{Gain}_{X_i}(D)$ as the mutual information $I(X_i;Y):=H(Y)-H(Y|X_i)$.

```
GainX1D = mutual_information(d,['X1'],['Y'])
GainX2D = mutual_information(d,['X2'],['Y'])
GainX3D = mutual_information(d,['X3'],['Y'])
GainX4D = mutual_information(d,['X4'],['Y'])
```

```
typeset()
Math(r'''
\begin{{aligned}}
\text{{Gain}}_{{X_1}}(D)&={:.3g}\\
\text{{Gain}}_{{X_2}}(D)&={:.3g}\\
\text{{Gain}}_{{X_3}}(D)&={:.3g}\\
\text{{Gain}}_{{X_4}}(D)&={:.3g}\\
\end{{aligned}}
'''.format(GainX1D,GainX2D,GainX3D,GainX4D))
```

$$
\begin{aligned}
\text{Gain}_{X_1}(D)&=0.5\\
\text{Gain}_{X_2}(D)&=1\\
\text{Gain}_{X_3}(D)&=1\\
\text{Gain}_{X_4}(D)&=1.31\\
\end{aligned}
$$

Calculate the information gain ratios:

```
typeset()
Math(r'''
\begin{{aligned}}
\frac{{\text{{Gain}}_{{X_1}}(D)}}{{\text{{SplitInfo}}_{{X_1}}(D)}}&={:.3g}\\
\frac{{\text{{Gain}}_{{X_2}}(D)}}{{\text{{SplitInfo}}_{{X_2}}(D)}}&={:.3g}\\
\frac{{\text{{Gain}}_{{X_3}}(D)}}{{\text{{SplitInfo}}_{{X_3}}(D)}}&={:.3g}\\
\frac{{\text{{Gain}}_{{X_4}}(D)}}{{\text{{SplitInfo}}_{{X_4}}(D)}}&={:.3g}\\
\end{{aligned}}
'''.format(GainX1D/SplitInfoX1D,GainX2D/SplitInfoX2D,GainX3D/SplitInfoX3D,GainX4D/SplitInfoX4D))
```

$$
\begin{aligned}
\frac{\text{Gain}_{X_1}(D)}{\text{SplitInfo}_{X_1}(D)}&=0.5\\
\frac{\text{Gain}_{X_2}(D)}{\text{SplitInfo}_{X_2}(D)}&=1\\
\frac{\text{Gain}_{X_3}(D)}{\text{SplitInfo}_{X_3}(D)}&=1\\
\frac{\text{Gain}_{X_4}(D)}{\text{SplitInfo}_{X_4}(D)}&=0.724\\
\end{aligned}
$$

## A summary of Shannon's information measures

Definitions:

\begin{align}
H(Y) &= E\left[\log \tfrac1{P_{Y}(Y)}\right] && \text{entropy of $Y$}\\
H(Y|X) &= E\left[\log \tfrac1{P_{Y|X}(Y|X)}\right] && \text{conditional entropy of $Y$ given $X$}\\
I(X;Y) &= E\left[\log \tfrac{P_{Y|X}(Y|X)}{P_Y(Y)}\right] && \text{mutual information of $X$ and $Y$}\\
\end{align}

Relationships among the information mesaures:

\begin{align}
H(X,Y)&=H(X)+H(Y|X) && \text{chain rule of entropy}\\
&=H(Y)+H(X|Y)\\
I(X;Y)&=H(Y)-H(Y|X) && \text{equivalent definitions of mutual information in terms of entropies}\\
&=H(X)+H(Y)-H(X,Y)\\
&=H(X)-H(X|Y)
\end{align}