Microentradas: Unicode/látex en la consola IPython/notebook Jupyter

[Este es un TIL que vi el otro día. Lo dejo aquí por si otros no lo sabían y les resulta útil.]

En la consola de IPython o en el notebook Jupyter podéis usar unicode escribiendo símbolos látex.

Por ejemplo, si escribes lo siguiente (en la consola o en una celda del notebook):

In [1]: \alpha

y pulsáis después la tecla tab veréis que se transforma a su símbolo látex y lo podéis usar fácilmente en vuestro código.

El resultado sería algo como lo siguiente (antes y después de pulsar la tecla tab):

  • Antes:

  • Después:

Esto puede ser útil para scripts propios, demos, formación,..., pero os lo desaconsejo en código en producción o a compartir 😉

Saludos.

Trabajando con Python y R

Trabajando de forma conjunta con Python y con R.

Hoy vamos a ver como podemos juntar lo bueno de R, algunas de sus librerías, con Python usando rpy2.

Pero, lo primero de todo, ¿qué es rpy2? rpy2 es una interfaz que permite que podamos comunicar información entre R y Python y que podamos acceder a funcionalidad de R desde Python. Por tanto, podemos estar usando Python para todo nuestro análisis y en el caso de que necesitemos alguna librería estadística especializada de R podremos acceder a la misma usando rpy2.

Para poder usar rpy2 necesitarás tener instalado tanto Python (CPython versión >= 2.7.x) como R (versión >=3), además de las librerías R a las que quieras acceder. Conda permite realizar todo el proceso de instalación de los intérpretes de Python y R, además de librerías, pero no he trabajado con Conda y R por lo que no puedo aportar mucho más en este aspecto. Supongo que será parecido a lo que hacemos con Conda y Python.

Para este microtutorial voy a hacer uso de la librería extRemes de R que permite hacer análisis de valores extremos usando varias de las metodologías más comúnmente aceptadas.

Como siempre, primero de todo, importaremos la funcionalidad que necesitamos para la ocasión.

# Importamos pandas y numpy para manejar los datos que pasaremos a R
import pandas as pd
import numpy as np

# Usamos rpy2 para interactuar con R
import rpy2.robjects as ro

# Activamos la conversión automática de tipos de rpy2
import rpy2.robjects.numpy2ri
rpy2.robjects.numpy2ri.activate()

import matplotlib.pyplot as plt
%matplotlib inline
En el anterior código podemos ver una serie de cosas nuevas que voy a explicar brevemente:

  • import rpy2.robjects as ro, esto lo explicaremos un poquito más abajo.
  • import rpy2.robjects.numpy2ri, importamos el módulo numpy2ri. Este módulo permite que hagamos conversión automática de objetos numpy a objetos rpy2.
  • rpy2.robjects.numpy2ri.activate(), hacemos uso de la función activate que activa la conversión automática de objetos que hemos comentado en la línea anterior.

Brevísima introducción a algunas de las cosas más importantes de rpy2.

Para evaluar directamente código R podemos hacerlo usando rpy2.robjects.r con el código R expresado como una cadena (rpy2.robjects lo he importado como ro en este caso, como podéis ver más arriba):

codigo_r = """
saluda <- function(cadena) {
  return(paste("Hola, ", cadena))
}
"""
ro.r(codigo_r)
<SignatureTranslatedFunction - Python:0x03096490 / R:0x03723E98>
En la anterior celda hemos creado una función R llamada saluda y que ahora está disponible en el espacio de nombres global de R. Podemos acceder a la misma desde Python de la siguiente forma:

saluda_py = ro.globalenv['saluda']
Y podemos usarla de la siguiente forma:

res = saluda_py('pepe')
print(res[0])
Hola,  pepe
En la anterior celda véis que para acceder al resultado he tenido que usar res[0]. En realidad, lo que nos devuleve rpy2 es:

print(type(res))
print(res.shape)
<class 'numpy.ndarray'>
(1,)
En este caso un numpy array con diversa información del objeto rpy2. Como el objeto solo devuelve un string pues el numpy array solo tiene un elemento.

Podemos acceder al código R de la función de la siguiente forma:

print(saluda_py.r_repr())
function (cadena) 
{
    return(paste("Hola, ", cadena))
}
Hemos visto como acceder desde Python a nombres disponibles en el entorno global de R. ¿Cómo podemos hacer para que algo que creemos en Python este accesible en R?

variable_r_creada_desde_python = ro.FloatVector(np.arange(1,5,0.1))
Veamos como es esta variable_r_creada_desde_python dentro de Python

variable_r_creada_desde_python
<FloatVector - Python:0x09D5A7B0 / R:0x07FE8900>
[1.000000, 1.100000, 1.200000, ..., 4.700000, 4.800000, 4.900000]
¿Y lo que se tendría que ver en R?

print(variable_r_creada_desde_python.r_repr())
c(1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2, 2.1, 2.2, 
2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3, 3.1, 3.2, 3.3, 3.4, 3.5, 
3.6, 3.7, 3.8, 3.9, 4, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 
4.9)
Pero ahora mismo esa variable no está disponible desde R y no la podríamos usar dentro de código R que permanece en el espacio R (vaya lío, ¿no?)

ro.r('variable_r_creada_desde_python')
---------------------------------------------------------------------------
RRuntimeError                             Traceback (most recent call last)
<ipython-input-10-524753a78365> in <module>()
----> 1 ro.r('variable_r_creada_desde_python')

d:\users\X003621\AppData\Local\Continuum\Miniconda3\lib\site-packages\rpy2\robjects\__init__.py in __call__(self, string)
    251     def __call__(self, string):
    252         p = rinterface.parse(string)
--> 253         res = self.eval(p)
    254         return res
    255 

d:\users\X003621\AppData\Local\Continuum\Miniconda3\lib\site-packages\rpy2\robjects\functions.py in __call__(self, *args, **kwargs)
    168                 v = kwargs.pop(k)
    169                 kwargs[r_k] = v
--> 170         return super(SignatureTranslatedFunction, self).__call__(*args, **kwargs)
    171 
    172 pattern_link = re.compile(r'\\link\{(.+?)\}')

d:\users\X003621\AppData\Local\Continuum\Miniconda3\lib\site-packages\rpy2\robjects\functions.py in __call__(self, *args, **kwargs)
     98         for k, v in kwargs.items():
     99             new_kwargs[k] = conversion.py2ri(v)
--> 100         res = super(Function, self).__call__(*new_args, **new_kwargs)
    101         res = conversion.ri2ro(res)
    102         return res

RRuntimeError: Error in eval(expr, envir, enclos) : 
  object 'variable_r_creada_desde_python' not found
Vale, tendremos que hacer que sea accesible desde R de la siguiente forma:

ro.globalenv["variable_ahora_en_r"] = variable_r_creada_desde_python
print(ro.r("variable_ahora_en_r"))
[ 1.   1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8  1.9  2.   2.1  2.2  2.3  2.4
  2.5  2.6  2.7  2.8  2.9  3.   3.1  3.2  3.3  3.4  3.5  3.6  3.7  3.8  3.9
  4.   4.1  4.2  4.3  4.4  4.5  4.6  4.7  4.8  4.9]
Ahora que ya la tenemos accesible la podemos usar desde R. Por ejemplo, vamos a usar la función sum en R que suma los elementos pero directamente desde R:

print(ro.r('sum(variable_ahora_en_r)'))
print(np.sum(variable_r_creada_desde_python))
[ 118.]
118.0
Perfecto, ya sabemos, de forma muy sencilla y básica, como podemos usar R desde Python, como podemos pasar información desde R hacia Python y desde Python hacia R. ¡¡¡Esto es muy poderoso!!!, estamos juntando lo mejor de dos mundos, la solidez de las herramientas científicas de Python con la funcionalidad especializada que nos pueden aportar algunas librerías de R no disponibles en otros ámbitos.

Trabajando de forma híbrida entre Python y R

Vamos a empezar importando la librería extRemes de R:

# Importamos la librería extRemes de R
from rpy2.robjects.packages import importr
extremes = importr('extRemes')
En la anterior celda hemos hecho lo siguiente:

  • from rpy2.robjects.packages import importr, La función importr nos servirá para importar las librerías R
  • extremes = importr('extRemes'), de esta forma importamos la librería extRemes de R, sería equivalente a hacer en R library(extRemes).
Leemos datos con pandas. En el mismo repo donde está este notebook está también un fichero de texto con datos que creé a priori. Supuestamente son datos horarios de velocidad del viento por lo que vamos a hacer análisis de valores extremos de velocidad del viento horaria.

data = pd.read_csv('datasets/Synthetic_data.txt',
                   sep = '\s*', skiprows = 1, parse_dates = [[0, 1]],
                   names = ['date','time','wspd'], index_col = 0)
data.head(3)
wspd
date_time
1983-01-01 00:00:00 7.9
1983-01-01 01:00:00 8.2
1983-01-01 02:00:00 8.5
Extraemos los máximos anuales los cuales usaremos posteriormente dentro de R para hacer cálculo de valores extremos usando la distribución generalizada de valores extremos (GEV):

max_y = data.wspd.groupby(pd.TimeGrouper(freq = 'A')).max()
Dibujamos los valores máximos anuales usando Pandas:

max_y.plot(kind = 'bar', figsize = (12, 4))
<matplotlib.axes._subplots.AxesSubplot at 0x10a923d0>
Referenciamos la funcionalidad fevd (fit extreme value distribution) dentro del paquete extremes de R para poder usarla directamente con los valores máximos que hemos obtenido usando Pandas y desde Python.

fevd = extremes.fevd
Como hemos comentado anteriormente, vamos a calcular los parámetros de la GEV usando el método de ajuste GMLE (Generalised Maximum Lihelihood Estimation) y los vamos a guardar directamente en una variable Python.

Veamos la ayuda antes:

print(fevd.__doc__)
Python representation of an R function.
description
-----------


 Fit a univariate extreme value distribution functions (e.g., GEV, GP, PP, Gumbel, or Exponential) to data; possibly with covariates in the parameters.
 


fevd(
    x,
    data,
    threshold = rinterface.NULL,
    threshold_fun = ~,
    location_fun = ~,
    scale_fun = ~,
    shape_fun = ~,
    use_phi = False,
    type = c,
    method = c,
    initial = rinterface.NULL,
    span,
    units = rinterface.NULL,
    time_units = days,
    period_basis = year,
    na_action = <rpy2.rinterface.SexpVector - Python:0x116D6C50 / R:0x0C4BB100>,
    optim_args = rinterface.NULL,
    priorFun = rinterface.NULL,
    priorParams = rinterface.NULL,
    proposalFun = rinterface.NULL,
    proposalParams = rinterface.NULL,
    iter = 9999.0,
    weights = 1.0,
    blocks = rinterface.NULL,
    verbose = False,
)

x :  `fevd`: `x` can be a numeric vector, the name of a column of `data` or a formula giving the data to which the EVD is to be fit.  In the case of the latter two, the `data` argument must be specified, and must have appropriately named columns.`plot` and `print` method functions: any list object returned by `fevd`. ,

object :  A list object of class \dQuote{fevd} as returned by `fevd`. ,

data :  A data frame object with named columns giving the data to be fit, as well as any data necessary for modeling non-stationarity through the threshold and/or any of the parameters. ,

threshold :  numeric (single or vector).  If fitting a peak over threshold (POT) model (i.e., `type` = \dQuote{PP}, \dQuote{GP}, \dQuote{Exponential}) this is the threshold over which (non-inclusive) data (or excesses) are used to estimate the parameters of the distribution function.  If the length is greater than 1, then the length must be equal to either the length of `x` (or number of rows of `data`) or to the number of unique arguments in `threshold.fun`. ,

threshold.fun :  formula describing a model for the thresholds using columns from `data`.  Any valid formula will work.  `data` must be supplied if this argument is anything other than ~ 1.  Not for use with `method` \dQuote{Lmoments}. ,

location.fun :  formula describing a model for each parameter using columns from `data`.  `data` must be supplied if any of these arguments are anything other than ~ 1. ,

scale.fun :  formula describing a model for each parameter using columns from `data`.  `data` must be supplied if any of these arguments are anything other than ~ 1. ,

shape.fun :  formula describing a model for each parameter using columns from `data`.  `data` must be supplied if any of these arguments are anything other than ~ 1. ,

use.phi :  logical; should the log of the scale parameter be used in the numerical optimization (for `method` \dQuote{MLE}, \dQuote{GMLE} and \dQuote{Bayesian} only)?  For the ML and GML estimation, this may make things more stable for some data. ,

type :  `fevd`: character stating which EVD to fit.  Default is to fit the generalized extreme value (GEV) distribution function (df).`plot` method function: character describing which plot(s) is (are) desired.  Default is \dQuote{primary}, which makes a 2 by 2 panel of plots including the QQ plot of the data quantiles against the fitted model quantiles (`type` \dQuote{qq}), a QQ plot (\dQuote{qq2}) of quantiles from model-simulated data against the data, a density plot of the data along with the model fitted density (`type` \dQuote{density}) and a return level plot (`type` \dQuote{rl}). In the case of a stationary (fixed) model, the return level plot will show return levels calculated for return periods given by `return.period`, along with associated CIs (calculated using default `method` arguments depending on the estimation method used in the fit.  For non-stationary models, the data are plotted as a line along with associated effective return levels for return periods of 2, 20 and 100 years (unless `return.period` is specified by the user to other values.  Other possible values for `type` include \dQuote{hist}, which is similar to \dQuote{density}, but shows the histogram for the data and \dQuote{trace}, which is not used for L-moment fits.  In the case of MLE/GMLE, the trace yields a panel of plots that show the negative log-likelihood and gradient negative log-likelihood (note that the MLE gradient is currently used even for GMLE) for each of the estimated parameter(s); allowing one parameter to vary according to `prange`, while the others remain fixed at their estimated values.  In the case of Bayesian estimation, the \dQuote{trace} option creates a panel of plots showing the posterior df and MCMC trace for each parameter. ,

method :  `fevd`: character naming which type of estimation method to use.  Default is to use maximum likelihood estimation (MLE). ,

initial :  A list object with any named parameter component giving the initial value estimates for starting the numerical optimization (MLE/GMLE) or the MCMC iterations (Bayesian).  In the case of MLE/GMLE, it is best to obtain a good intial guess, and in the Bayesian case, it is perhaps better to choose poor initial estimates.  If NULL (default), then L-moments estimates and estimates based on Gumbel moments will be calculated, and whichever yields the lowest negative log-likelihood is used.  In the case of `type` \dQuote{PP}, an additional MLE/GMLE estimate is made for the generalized Pareto (GP) df, and parameters are converted to those of the Poisson Process (PP) model.  Again, the initial estimates yielding the lowest negative log-likelihoo value are used for the initial guess. ,

span :  single numeric giving the number of years (or other desired temporal unit) in the data set.  Only used for POT models, and only important in the estimation for the PP model, but important for subsequent estimates of return levels for any POT model.  If missing, it will be calculated using information from `time.units`. ,

units :  (optional) character giving the units of the data, which if given may be used subsequently (e.g., on plot axis labels, etc.). ,

time.units :  character string that must be one of \dQuote{hours}, \dQuote{minutes}, \dQuote{seconds}, \dQuote{days}, \dQuote{months}, \dQuote{years}, \dQuote{m/hour}, \dQuote{m/minute}, \dQuote{m/second}, \dQuote{m/day}, \dQuote{m/month}, or \dQuote{m/year}; where m is a number.  If `span` is missing, then this argument is used in determining the value of `span`.  It is also returned with the output and used subsequently for plot labelling, etc. ,

period.basis :  character string giving the units for the period.  Used only for plot labelling and naming output vectors from some of the method functions (e.g., for establishing what the period represents for the return period). ,

rperiods :  numeric vector giving the return period(s) for which it is desired to calculate the corresponding return levels. ,

period :  character string naming the units for the return period. ,

burn.in :  The first `burn.in` values are thrown out before calculating anything from the MCMC sample. ,

a :  when plotting empirical probabilies and such, the function `ppoints` is called, which has this argument `a`. ,

d :  numeric determining how to scale the rate parameter for the point process.  If NULL, the function will attempt to scale based on the values of `period.basis` and `time.units`, the first of which must be \dQuote{year} and the second of which must be one of \dQuote{days}, \dQuote{months}, \dQuote{years}, \dQuote{hours}, \dQuote{minutes} or \dQuote{seconds}.  If none of these are the case, then `d` should be specified, otherwise, it is not necessary. ,

density.args :  named list object containing arguments to the `density` and `hist` functions, respectively. ,

hist.args :  named list object containing arguments to the `density` and `hist` functions, respectively. ,

na.action :  function to be called to handle missing values.  Generally, this should remain at the default (na.fail), and the user should take care to impute missing values in an appropriate manner as it may have serious consequences on the results. ,

optim.args :  A list with named components matching exactly any arguments that the user wishes to specify to `optim`, which is used only for MLE and GMLE methods.  By default, the \dQuote{BFGS} method is used along with `grlevd` for the gradient argument.  Generally, the `grlevd` function is used for the `gr` option unless the user specifies otherwise, or the optimization method does not take gradient information. ,

priorFun :  character naming a prior df to use for methods GMLE and Bayesian.  The default for GMLE (not including Gumbel or Exponential types) is to use the one suggested by Martins and Stedinger (2000, 2001) on the shape parameter; a beta df on -0.5 to 0.5 with parameters `p` and `q`.  Must take `x` as its first argument for `method` \dQuote{GMLE}.  Optional arguments for the default function are `p` and `q` (see details section).The default for Bayesian estimation is to use normal distribution functions.  For Bayesian estimation, this function must take `theta` as its first argument.Note: if this argument is not NULL and `method` is set to \dQuote{MLE}, it will be changed to \dQuote{GMLE}. ,

priorParams :  named list containing any prior df parameters (where the list names are the same as the function argument names).  Default for GMLE (assuming the default function is used) is to use `q` = 6 and `p` = 9.  Note that in the Martins and Stedinger (2000, 2001) papers, they use a different EVD parametrization than is used here such that a positive shape parameter gives the upper bounded distribution instead of the heavy-tail one (as emloyed here).  To be consistent with these papers, `p` and `q` are reversed inside the code so that they have the same interpretation as in the papers.Default for Bayesian estimation is to use ML estimates for the means of each parameter (may be changed using `m`, which must be a vector of same length as the number of parameters to be estimated (i.e., if using the default prior df)) and a standard deviation of 10 for all other parameters (again, if using the default prior df, may be changed using `v`, which must be a vector of length equal to the number of parameters). ,

proposalFun :  For Bayesian estimation only, this is a character naming a function used to generate proposal parameters at each iteration of the MCMC.  If NULL (default), a random walk chain is used whereby if theta.i is the current value of the parameter, the proposed new parameter theta.star is given by theta.i + z, where z is drawn at random from a normal df. ,

proposalParams :  A named list object describing any optional arguments to the `proposalFun` function.  All functions must take argument `p`, which must be a vector of the parameters, and `ind`, which is used to identify which parameter is to be proposed.  The default `proposalFun` function takes additional arguments `mean` and `sd`, which must be vectors of length equal to the number of parameters in the model (default is to use zero for the mean of z for every parameter and 0.1 for its standard deviation). ,

iter :  Used only for Bayesian estimation, this is the number of MCMC iterations to do. ,

weights :  numeric of length 1 or n giving weights to be applied     in the likelihood calculations (e.g., if there are data points to     be weighted more/less heavily than others). ,

blocks :  An optional list containing information required to fit point process models in a computationally-efficient manner by using only the exceedances and not the observations below the threshold(s). See details for further information.       ,

FUN :  character string naming a function to use to estimate the parameters from the MCMC sample.  The function is applied to each column of the `results` component of the returned `fevd` object. ,

verbose :  logical; should progress information be printed to the screen?  If TRUE, for MLE/GMLE, the argument `trace` will be set to 6 in the call to `optim`. ,

prange :  matrix whose columns are numeric vectors of length two for each parameter in the model giving the parameter range over which trace plots should be made.  Default is to use either +/- 2 * std. err. of the parameter (first choice) or, if the standard error cannot be calculated, then +/- 2 * log2(abs(parameter)).  Typically, these values seem to work very well for these plots. ,

... :  Not used by most functions here.  Optional arguments to `plot` for the various `plot` method functions.In the case of the `summary` method functions, the logical argument `silent` may be passed to suppress (if TRUE) printing any information to the screen. ,

Y ahora vamos a hacer un cálculo sin meternos mucho en todas las opciones posibles.

res = fevd(max_y.values, type = "GEV", method = "GMLE")
¿Qué estructura tiene la variable res que acabamos de crear y que tiene los resultados del ajuste?

print(type(res))
<class 'rpy2.robjects.vectors.ListVector'>
print(res.r_repr)
<bound method ListVector.r_repr of <ListVector - Python:0x10AB8878 / R:0x0CA9B458>
[Vector, ndarray, ndarray, ..., ndarray, ListV..., ListV...]
  call: <class 'rpy2.robjects.vectors.Vector'>
  <Vector - Python:0x10AB8418 / R:0x0CB2FFB4>
[RNULLType, Vector, Vector, Vector]
  data.name: <class 'numpy.ndarray'>
  array(['structure(c(22.2, 25.5, 21.5, 22.5, 23.7, 22.5, 21.7, 29.7, 24.2, ',
       '23.8, 28.1, 23.4, 23.7, 25.6, 23.2, 24.9, 22.8, 24.6, 22.3, 25.5, ',
       '22.6, 24, 20.8, 23.5, 24.4, 24.1, 25.1, 19.4, 22.8, 24.2, 25, ',
       '25.3), .Dim = 32L)', ''], 
      dtype='<U66')
  weights: <class 'numpy.ndarray'>
  array([ 1.])
  ...
  call: <class 'numpy.ndarray'>
  array(['location', 'scale', 'shape'], 
      dtype='<U8')
<ListVector - Python:0x10AB8878 / R:0x0CA9B458>
[Vector, ndarray, ndarray, ..., ndarray, ListV..., ListV...]
<ListVector - Python:0x10AB8878 / R:0x0CA9B458>
[Vector, ndarray, ndarray, ..., ndarray, ListV..., ListV...]>
Según nos indica lo anterior, ahora res es un vector que está compuesto de diferentes elementos. Los vectores pueden tener un nombre para todos o algunos de los elementos. Para acceder a estor nombres podemos hacer:

res.names
array(['call', 'data.name', 'weights', 'in.data', 'x', 'priorFun',
       'priorParams', 'method', 'type', 'period.basis', 'par.models',
       'const.loc', 'const.scale', 'const.shape', 'n', 'na.action',
       'parnames', 'results', 'initial.results'], 
      dtype='<U15')
Según el output anterior, parece que hay un nombre results, ahí es donde se guardan los valores del ajuste, los estimadores. Para acceder al mismo podemos hacerlo de diferentes formas. Con Python tendriamos que saber el índice y acceder de forma normal (__getitem__()). Existe una forma alternativa usando el método rx que nos permite acceder directamente con el nombre:

results = res.rx('results')
print(results.r_repr)
<bound method ListVector.r_repr of <ListVector - Python:0x10ABBCB0 / R:0x0CBFBC40>
[ListVector]
<ListVector - Python:0x10ABBCB0 / R:0x0CBFBC40>
[ListVector]>
Parece que tenemos un único elemento:

results = results[0]
results.r_repr
<bound method ListVector.r_repr of <ListVector - Python:0x10ABF490 / R:0x0C851BA0>
[ndarray, ndarray, ndarray, ..., RNULL..., ndarray, ListV...]
  par: <class 'numpy.ndarray'>
  array([ 23.06394152,   1.75769129,  -0.16288164])
  value: <class 'numpy.ndarray'>
  array([  1.00000000e+16])
  counts: <class 'numpy.ndarray'>
  array([1, 1], dtype=int32)
  ...
  par: <class 'rpy2.rinterface.RNULLType'>
  rpy2.rinterface.NULL
  value: <class 'numpy.ndarray'>
  array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
<ListVector - Python:0x10ABF490 / R:0x0C851BA0>
[ndarray, ndarray, ndarray, ..., RNULL..., ndarray, ListV...]>
Vemos ahora que results tiene un elemento con nombre par donde se guardan los valores de los estimadores del ajuste a la GEV que hemos obtenido usando GMLE. Vamos a obtener finalmente los valores de los estimadores:

location, scale, shape = results.rx('par')[0][:]
print(location, scale, shape)
23.0639415199 1.75769128743 -0.162881636772

Funcion mágica para R (antigua rmagic)

Usamos la antigua función mágica rmagic que ahora se activará en el notebook de la siguiente forma:

%load_ext rpy2.ipython
Veamos como funciona la functión mágica de R:

help(rpy2.ipython.rmagic.RMagics.R)
Help on function R in module rpy2.ipython.rmagic:

R(self, line, cell=None, local_ns=None)
    ::
    
      %R [-i INPUT] [-o OUTPUT] [-n] [-w WIDTH] [-h HEIGHT] [-p POINTSIZE]
             [-b BG] [--noisolation] [-u {px,in,cm,mm}] [-r RES]
             
] Execute code in R, optionally returning results to the Python runtime. In line mode, this will evaluate an expression and convert the returned value to a Python object. The return value is determined by rpy2's behaviour of returning the result of evaluating the final expression. Multiple R expressions can be executed by joining them with semicolons:: In [9]: %R X=c(1,4,5,7); sd(X); mean(X) Out[9]: array([ 4.25]) In cell mode, this will run a block of R code. The resulting value is printed if it would printed be when evaluating the same code within a standard R REPL. Nothing is returned to python by default in cell mode:: In [10]: %%R ....: Y = c(2,4,3,9) ....: summary(lm(Y~X)) Call: lm(formula = Y ~ X) Residuals: 1 2 3 4 0.88 -0.24 -2.28 1.64 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 0.0800 2.3000 0.035 0.975 X 1.0400 0.4822 2.157 0.164 Residual standard error: 2.088 on 2 degrees of freedom Multiple R-squared: 0.6993,Adjusted R-squared: 0.549 F-statistic: 4.651 on 1 and 2 DF, p-value: 0.1638 In the notebook, plots are published as the output of the cell:: %R plot(X, Y) will create a scatter plot of X bs Y. If cell is not None and line has some R code, it is prepended to the R code in cell. Objects can be passed back and forth between rpy2 and python via the -i -o flags in line:: In [14]: Z = np.array([1,4,5,10]) In [15]: %R -i Z mean(Z) Out[15]: array([ 5.]) In [16]: %R -o W W=Z*mean(Z) Out[16]: array([ 5., 20., 25., 50.]) In [17]: W Out[17]: array([ 5., 20., 25., 50.]) The return value is determined by these rules: * If the cell is not None (i.e., has contents), the magic returns None. * If the final line results in a NULL value when evaluated by rpy2, then None is returned. * No attempt is made to convert the final value to a structured array. Use %Rget to push a structured array. * If the -n flag is present, there is no return value. * A trailing ';' will also result in no return value as the last value in the line is an empty string. optional arguments: -i INPUT, --input INPUT Names of input variable from shell.user_ns to be assigned to R variables of the same names after calling self.pyconverter. Multiple names can be passed separated only by commas with no whitespace. -o OUTPUT, --output OUTPUT Names of variables to be pushed from rpy2 to shell.user_ns after executing cell body (rpy2's internal facilities will apply ri2ro as appropriate). Multiple names can be passed separated only by commas with no whitespace. -n, --noreturn Force the magic to not return anything. Plot: Arguments to plotting device -w WIDTH, --width WIDTH Width of plotting device in R. -h HEIGHT, --height HEIGHT Height of plotting device in R. -p POINTSIZE, --pointsize POINTSIZE Pointsize of plotting device in R. -b BG, --bg BG Background of plotting device in R. SVG: SVG specific arguments --noisolation Disable SVG isolation in the Notebook. By default, SVGs are isolated to avoid namespace collisions between figures.Disabling SVG isolation allows to reference previous figures or share CSS rules across a set of SVGs. PNG: PNG specific arguments -u <{px,in,cm,mm}>, --units <{px,in,cm,mm}> Units of png plotting device sent as an argument to *png* in R. One of ["px", "in", "cm", "mm"]. -r RES, --res RES Resolution of png plotting device sent as an argument to *png* in R. Defaults to 72 if *units* is one of ["in", "cm", "mm"]. code
A veces, será más simple usar la función mágica para interactuar con R. Veamos un ejemplo donde le pasamos a R el valor obtenido de la función fevd del paquete extRemes de R que he usado anteriormente y corremos cierto código directamente desde R sin tener que usar ro.r.

%R -i res plot.fevd(res)
En la anterior celda de código le he pasado como parámetro de entrada (- i res) la variable res que había obtenido anteriormente para que esté disponible desde R. y he ejecutado código R puro (plot.fevd(res)).

Si lo anterior lo quiero hacer con rpy2 puedo hacer lo siquiente:

CUIDADO, la siguiente celda de código puede provocar que se reinicialice el notebook y se rompa la sesión. Si has hecho cambios en el notebook guárdalos antes de ejecutar la celda, por lo que pueda pasar...

ro.globalenv['res'] = res
ro.r("plot.fevd(res)")
rpy2.rinterface.NULL
Lo anterior me bloquea el notebook y me 'rompe' la sesión (en windows, al menos) ya que la ventana de gráficos se abre de forma externa... Por tanto, una buena opción para trabajar de forma interactiva con Python y R de forma conjunta y que no se 'rompa' nada es usar tanto rpy2 como su extensión para el notebook de Jupyter (dejaremos de llamarlo IPython poco a poco).

Usando Python y R combinando rpy2 y la función mágica

Vamos a combinar las dos formas de trabajar con rpy2 en el siguiente ejemplo:

metodos = ["MLE", "GMLE"]
tipos = ["GEV", "Gumbel"]
Lo que vamos a hacer es calcular los parámetros del ajuste usando la distribución GEV y Gumbel, que es un caso especial de la GEV. El ajuste lo calculamos usando tanto MLE como GMLE. Además de mostrar los valores resultantes del ajuste para los estimadores vamos a mostrar el dibujo de cada uno de los ajustes y algunos test de bondad. Usamos Python para toda la maquinaria de los bucles, usamos rpy2 para obtener los estimadores y usamos la función mágica de rpy2 para mostrar los gráficos del resultado.

for t in tipos:
    for m in metodos:
        print('tipo de ajuste: ', t)
        print('método de ajuste: ', m)
        res = fevd(max_y.values, method = m, type = t)
        if m == "Bayesian":
            print(res.rx('results')[0][-1][0:-2])
        elif m == "Lmoments":
            print(res.rx('results')[0])
        else:
            print(res.rx('results')[0].rx('par')[0][:])
        %R -i res plot.fevd(res)
tipo de ajuste:  GEV
método de ajuste:  MLE
[ 23.05170779   1.80858528  -0.14979836]
tipo de ajuste:  GEV
método de ajuste:  GMLE
[ 23.06394152   1.75769129  -0.16288164]
tipo de ajuste:  Gumbel
método de ajuste:  MLE
[ 22.90587606   1.81445179]
tipo de ajuste:  Gumbel
método de ajuste:  GMLE
[ 22.90587606   1.81445179]

Comentarios finales

Espero que este microtutorial os valga, al menos, para conocer rpy2 y la potencia que os puede llegar a aportar a vuestros análisis 'pythónicos'. Como resumen:

  • Tenemos en nuestras manos una herramienta muy poderosa.
  • Rpy2 puede estar poco madura en algún aspecto aunque ha mejorado bastante con respecto a alguna versión de rpy2 que usé anteriormente.
  • También podéis usar directamente R como kernel, aunque perdéis la interacción con Python. También puede ocurrir que la instalación os haga perder mucho el tiempo para poder hacerlo funcionar si os veis obligados a usarlo desde windows.
  • En la elaboración de este microtutorial la consola de R donde iba haciendo algunas pruebas simples se me ha 'roto' muchísimas más veces de las que consideraría aceptables. No se puede quedar colgada, cerrar,..., seis o siete veces en media hora una consola haciendo cosas simples. Eso hace que si quieres usar R de forma interactiva debas usar alternativas como Jupyter, RStudio u otros que desconozco ya que la consola oficial no está 'ni pa pipas' (por lo menos en Windows, el sistema operativo con más usuarios potenciales, mal que me pese).
  • Sigo manteniendo muchas reservas respecto a R como Lenguaje de Programación (en mayúsculas) por lo que si puedo limitar su uso a alguna librería especializada que necesito y a la que pueda acceder con rpy2 es lo que seguiré haciendo (If you are using R and you think you're in hell, this is a map for you.)

Y el notebook...

En el caso de que queráis trastear con el notebook lo podéis descargar desde aquí.

También podéis descargar todos los notebooks desde nuestro repo oficial de notebooks.

tutormagic: Jupyter + pythontutor

Esta será una microentrada para presentar una extensión para el notebook que estoy usando en un curso interno que estoy dando en mi empresa.
Si a alguno más os puede valer para mostrar cosas básicas de Python (2 y 3, además de Java y Javascript) para muy principiantes me alegro.

Nombre en clave: tutormagic

Esta extensión lo único que hace es embeber dentro de un IFrame la página de pythontutor usando el código que hayamos definido en una celda de código precedida de la cell magic %%tutor.
Como he comentado anteriormente, se puede escribir código Python2, Python3, Java y Javascript, que son los lenguajes soportados por pythontutor.

Ejemplo

Primero deberemos instalar la extensión. Está disponible en pypi por lo que la podéis instalar usando pip install tutormagic. Una vez instalada, dentro de un notebook de IPython la deberías cargar usando:

%load_ext tutormagic
Una vez hecho esto ya deberiamos tener disponible la cell magic para ser usada. Podéis ver un ejemplo en este notebook.

Y eso es todo

Lo dicho, espero que sea útil para alguien.

Saludos.

Anunciado coLaboratory: IPython/Jupyter + Google

Google anunció ayer oficialmente el proyecto coLaboratory, una nueva herramienta para «hacer análisis de datos» que no es ni más ni menos que la integración del notebook de IPython/Jupyter y los productos de Google, especialmente Google Drive.

El ecosistema Python científico sigue avanzando a un ritmo frenético y el proyecto IPython (que ahora se llama Jupyter) va a la cabeza. Después de que Fernando Pérez anunciase en la última y épica SciPy celebrada en Austin, TX que iban a separar la parte del notebook que no depende del lenguaje para crear el proyecto Jupyter, ahora se ven los frutos de las nuevas alianzas que ha creado el equipo de desarrollo y que seguramente supondrán una revolución en el corto plazo.

Como explican desde Google, coLaboratory corre en Google Chrome gracias a su tecnología PNaCl y la aplicación para el navegador lleva incorporadas todas las bibliotecas de Python necesarias, de tal forma que es extremadamente fácil de instalar. Será posible editar notebooks como si fuesen documentos de Google Drive y colaborar entre varias personas.

Nuevo notebook
Nuevo notebook

Aunque sin duda lo más impactante es la posibilidad de crear componentes gráficos interactivos a partir de código. Como se ve en la imagen, en el código de una celda podemos añadir unos comentarios especiales que generan elementos como deslizadores, menús desplegables... y que el programador puede ocultar el código y mostrar solo esa parte gráfica, mejorando enormemente la experiencia del usuario no programador.

Las posibilidades de coLaboratory parecen infinitas. Yo al menos no lo probaré hasta después del verano, pero si alguien se atreve a instalar Chrome beta y a hacer sus primeros experimentos que no dude en contarnos sus experiencias e incluso mandarnos un artículo al blog 😉

Tutorial de Highcharts usando IPython, brython y brythonmagic

Hoy volvemos a hablar de una librería Javascript (ver tutorial de Openlayers) pero lo vamos a hacer desde un punto de vista un poco más pythónico (aunque por debajo no dejará de ser Javascript).

En el siguiente frame tenéis un tutorial de Highcharts (librería para hacer gráficos interactivos en el navegador):

Si lo queréis ver a pantalla completa podéis pulsar aquí.

Si alguien tiene alguna duda que pregunte en los comentarios.

Saludos a todos.

Presentando brythonmagic

En esta entrada os voy a enseñar una extensión que he creado que funciona sobre el IPython notebook, es decir, está pensada para funcionar en un navegador. Si no sabéis muy bien que son las 'cell magics' (no sé muy bien como traducir esto, podría ser algo así como magia que aplica sobre celdas) le podéis echar un ojo a esta entrada donde ya creamos alguna función mágica ('line magics') de ayuda. De todas formas, entre mis planes está el hacer una parte de la serie '157 cosas de IPython...' que hable sobre las 'cell magics'. No han salido nuevos capítulos ya que estoy viendo como evoluciona IPython 2.0.0 y adaptar las entradas a la nueva versión de IPython para que no se queden desfasadas ya de salida...

Bueno, a lo que vamos,... ¿Qué es eso de brythonmagic? Brythonmagic pretende ayudar a los programadores Python a crear un puente entre el mundo de la programación científica en Python y el de la visualización interactiva en javascript a través del IPython notebook y con la ayuda de Brython, una implementación de Python3 hecha en javascript y en Python y que funciona en el lado del cliente (navegador web). De esa forma, escribiendo en Python/subconjunto de Python/seudoPython/... (i.e., escribiendo en Brython) podemos acceder al navegador y a interesantes librerías javascript que funcionan en el navegador.

Brython es un proyecto que ya empieza a madurar y nos permite hacer cosas bastante interesantes que hasta ahora estaban vetadas para los pythonistas. Mediante brythonmagic he llevado las capacidades de Brython al notebook de IPython creando una 'cell magic', %%brython, que permite ejecutar código Brython dentro del navegador y nos permite manejar documentos HTML, acceder al DOM,...

El funcionamiento es muy sencillo, añades la extensión al notebook de IPython, añades las librerías de Brython que vayas a necesitar y después ya puedes ejecutar celdas de código Brython dentro del notebook y ver el resultado al momento.

A día de hoy permite una serie de opciones:

  • Obtener, mediante pantalla, el html que se usa para el output de la celda de código
  • Indicar que librerías javascript vamos a usar en la celda de código
  • Usar datos de Python creados en otras celdas y que queramos usar en la celda de código Brython
  • Indicar el nombre del contenedor (elemento HTML div) de la salida.

En el siguiente vídeo podéis echarle un vistazo rápido a lo que se puede hacer con brythonmagic. En el vídeo, entre otras cosas, accedemos a las librerías d3js (gráficos interactivos) y openlayers (mapas interactivos):

[youtube=www.youtube.com/watch?v=adQzjuUX0kw]

El notebook usado en el vídeo está en el repositorio por si lo queréis ejecutar de forma local.

El repo de Brython lo tenéis aquí.

Además de para usarse para lo comentado anteriormente también lo podéis usar para probar cosas de Brython en un entorno amigable como el notebook de IPython (resaltado de sintáxis, autocompletado, formateo del código, comentarios en Markdown,...), es decir, si eres un 'frontender' y quieres probar Brython puedes usar brythonmagic y IPython para ello :-).

Saludos.

P.D.: Se agradecen ideas para mejorar brythonmagic.

157 cosas de IPython que no sabías y nunca preguntaste (II)

En la primera entrega vimos como usar la ayuda de IPython y como personalizar nuestras propias funciones mágicas de ayuda.

En esta segunda entrega vamos a hablar del uso de la historia dentro de IPython.

[Inciso: Os recomiendo que veáis lo que viene a continuación en el notebook o en nbviewer. WordPress.com cada vez me parece más limitado para mostrar el contenido que mostramos en este blog]

IPython guarda la historia de los comandos que se usan en cada línea/celda de cada sesión bajo un determinado perfil. Esta información se guarda en una base de datos sqlite. Por defecto, se guardan el inicio y fin de sesión, los comandos usados en cada sesión y algunos metadatos más. IPython también se puede configurar para que almacene los outputs.

Historia

La base de datos con la historia se guarda en la carpeta que obtenéis haciendo lo siguiente:

from IPython.utils import path
path.locate_profile()

'/home/kiko/.config/ipython/profile_default'

Y la base de datos se guardará en esa carpeta con el nombre history.sqlite.

Una forma alternativa de obtener la ruta a la historia bajo el perfil actual es usando lo siguiente:

get_ipython().history_manager.hist_file

'/home/kiko/.config/ipython/profile_default/history.sqlite'

Acceso a determinadas celdas de la historia

Para reusar comandos ya usados podemos hacer uso de la historia que guarda IPython en cada sesión.

  • Podemos usar las teclas de cursor hacia arriba o hacia abajo para recorrer los últimos comandos usados (esto no funciona en el notebook pero sí en la consola de IPython).
  • Si escribimos algo en la línea de comandos y pulsamos el cursor hacia arriba nos mostrará solo lo que comience por lo ya escrito en la línea de comandos (nuevamente, esto no funciona en el notebook pero sí en la consola de IPython).
  • En las sesiones interactivas, el input y el output se guarda en las variables In y Out. Poniendo el índice de la línea usada nos volverá a ejecutar esa línea, en el caso de que usemos la variable In o nos mostrará el output en caso de que usemos la variable Out. In es una lista mientras que Out es un diccionario. En el caso de que no haya output para un número de línea nos dará un KeyError. Por ejemplo, veamos las siguientes celdas de código:
In?

Nos mostrará en pantalla lo siguiente (esto puede variar en función de lo que hayáis escrito previamente en la consola o el notebook, lo siguiente solo sirve como ejemplo):

Type:       list
String Form:['', "get_ipython().magic('pinfo In')", "get_ipython().magic('pinfo In')"]
 Length:     3
 Docstring:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items

Mientras que si hacemos lo mismo para Out obtendremos la siguiente info (esto puede variar en función de lo que hayáis escrito previamente en la consola o el notebook, lo siguiente solo sirve como ejemplo):

Type:       dict
String Form:{}
Length:     0
Docstring:
dict() -> new empty dictionary
dict(mapping) -> new dictionary initialized from a mapping object's
    (key, value) pairs
dict(iterable) -> new dictionary initialized as if via:
d = {}
for k, v in iterable:
    d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)

Si ahora hacemos lo siguiente:

a = 2
print(a)

Nos mostrará un 2 en pantalla pero no lo guarda en el Output puesto que solo es visualización. Por tanto, lo siguiente:

Out

Nos mostrará (esto puede variar en función de lo que hayáis escrito previamente en la consola o el notebook, lo siguiente solo sirve como ejemplo):

{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default'}

Vemos que en Out se encuentra lo que hemos obtenido en las primeras celdas de código. Si ahora queremos ver otro output podemos hacer lo siguiente:

a

Y, como no lo hemos mostrado con un print, se añade al Output.

Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite',
 7: 2}

Vemos que ya tenemos algún valor para el Out y ya podremos acceder a ese valor por si lo quisiéramos usar en alguna otra celda. Por ejemplo, de la siguiente forma:

b = Out[7]
print(b)

Nos mostraría 2 en pantalla.

Como el In siempre lo tendremos, en lugar de ser un diccionario es una lista y podemos acceder al valor de la celda usando el índice de la misma. Por ejemplo:

for celda in In:
    print(celda)

Nos mostraría lo siguiente (o algo aproximado a lo siguiente):

from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
get_ipython().magic('pinfo In')
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)
for celda in In:
    print(celda)

También podemos acceder a los tres últimos outputs de las tres últimas celdas usando _, __, ___, que nos mostrará el output de la última, penúltima o antepenúltima celdas usadas, respectivamente.

print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')
antepenúltima:
 /home/kiko/.config/ipython/profile_default 
penúltima:
 /home/kiko/.config/ipython/profile_default/history.sqlite 
última:
 2

Si queremos acceder a los inputs de las últimas celdas podemos usar algo parecido pero de la siguiente forma, _i, _ii o _iii para la última, penúltima o antepenúltima celda de input:

print('antepenúltima:n', _iii, 'nn')
print('penúltima:n', _ii, 'nn')
print('última:n', _i, 'nn')
antepenúltima:
 print(b) 
penúltima:
 for celda in In:
    print(celda) 
última:
print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')

Análogamente a lo visto anteriormente, podemos usar _n o _in para mostrar, respectivamente, el output o el input de la celda n. Por ejemplo, para ver el input y el output de la celda anterior (en este caso sería la 11) podemos hacer lo siguiente:

print('El input de la celda 11 es:')
print(_i11)

Que nos mostrará:

El input de la celda 11 es:
for celda in In:
    print(celda)

Y para el output:

print('Te he engañado. No existe output para la celda 11')
print('Si intentas acceder al valor _11 obtendrás un NameError ya que no existe la variable')
print('pero te puedo enseñar el de la celda 7:')
print(_7)
Te he engañado. No existe output para la celda 11
Si intentas acceder al valor _11 obtendrás un NameError ya que no existe la variable
pero te puedo enseñar el de la celda 7:
2

Lo anterior es equivalente a usar In[n] o Out[n]. Una tercera alternativa, además, sería usar _ih[n] para los inputs y _oh[n] para los outputs.

In[11]

Mostrará:

'for celda in In:n    print(celda)'

Mientras que:

_ih[11]

Nos mostrará lo mismo:

'for celda in In:n    print(celda)'

Acceso a bloques de historia

Para acceder a toda la historia de la sesión actual podemos usar las funciones mágicas %history o %hist, que es un alias.

Podemos obtener toda la historia o solo una porción. Por ejemplo, el siguiente comando nos mostrará la historia desde la celda 1 a la 10 en la sesión actual:

%hist 1-10
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)

Si, además de acceder a las celdas 1 a 10, queremos acceder a celdas sueltas podemos usar la siguiente notación para acceder a las celdas 12 y 14 (además de a las 10 primeras).

%hist 1-10 12 14
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)
print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')
print('El input de la celda 11 es:')
print(_i11)

Si ahora queremos acceder a todas las celdas donde hayamos usado, por ejemplo, un comando que incluya 'a = 1' podemos hacer uso de la opción -g (similar a grep) de la siguiente forma (la salida dependerá de vuestra historia):

%hist -g a = 1
29/129: a = 1
29/133: a = 1
29/136: a = 1.1
29/138: a = 1
29/142: a = 1.1
29/145: a = 1
29/147: a = 1
29/149: a = 1
29/151: a = 1
51/7: a = 1
185/1: a = 1
187/19: %hist -g a = 1
187/20: %hist -g [a = 1]
187/21: %hist -g a = 1
188/20: %hist -g a = 1
189/2: a = 1
190/1: a = 1
190/3: a = 1
190/4: a = 1
190/10: a = 1
201/1:
code = """
def hello():
    if a = 1:
        print(a)
    elseif a =2:
        print(a)
    else:
        print('kk')
    return None
"""
201/3:
code = """
def hello():
    if a = 1:
        print(a)
    elif a =2:
        print(a)
    else:
        print('kk')
    return None
"""
201/5:
code = """
def hello():
    if a = 1:
        print(a)
    elif a = 2:
        print(a)
    else:
        print('kk')
    return None
"""
201/14:
a = 1
b = exec(code)
201/15:
a = 1
b = exec(code)
print(b)
201/16:
a = 1
b = exec(code)
print(c)
201/17:
a = 1
b = exec(code)
print(b)
201/43: def a():a = 1;return a
201/45: def a():a = 1;b=2return a
201/46: def a():a = 1;b=2;return a,b
201/81:
a = 1
code = """
def hello():
    if a == 1:
        print('a=1')
    elif a == 2:
        print('a=2')
    else:
        if a == 3:
            print('a=3')
    return None
"""
201/85:
a = 1
code = """
def hello():
    if a == 1:
        print('a=1')
    elif a == 2:
        print('a=2')
    else:
        if a == 3:
            print('a=3')
    return None
"""
201/93:
a = 1
code = """
def hello():
    if a == 1:
        return('a=1')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
"""
201/135:
a = 2
code = """
a = 1
def hello():
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
hello()
"""
201/157:
a = 2
code = """
a = 1
def hello():
    """Hola"""
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    """Hola"""
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/158:
a = 2
code = """
a = 1
def hello():
    '''Hola'''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    """Hola"""
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/159:
a = 2
code = """
a = 1
def hello():
    '''Hola'''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/174:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/219:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
print(hello())
help(A)
"""
201/230:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
        pass
print(hello())
help(A)
"""
201/233:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
        pass
print(hello())
help(A)
"""
201/237:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
        pass
    def _kk(self):
        'Adios'
        pass
print(hello())
help(A)
"""
202/2: a = 1
202/3: a = 1
202/4: a = 1
202/5:
a = 1
print(a)
214/20: %hist -g a = 1
  20: %hist -g a = 1

[Gracias a la historia sé que escribo demasiado código estúpido... :-(]

Pero esta busqueda no se restringe a la historia de la sesión actual sino que buscará en toda la historia almacenada por IPython bajo el perfil que estemos usando. El anterior output indica la sesión, el número de línea/celda de esa sesión y el código usado en esa línea/celda con la siguiente notación:

Sesión/celda: Código_introducido_en_la_celda

En este caso, podéis ver que en la última línea no se indica el número de sesión puesto que se refiere a la sesión actual:

Si usamos la opción -o también obtendremos la historia con el output incluido. Podéis ver el siguiente ejemplo para ver como funciona:

%hist -o
from IPython.utils import path
path.locate_profile()
'/home/kiko/.config/ipython/profile_default'
get_ipython().history_manager.hist_file
'/home/kiko/.config/ipython/profile_default/history.sqlite'
In?
a = 2
print(a)
Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite'}
a
2
Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite',
 7: 2}
b = Out[7]
print(b)
for celda in In:
    print(celda)
print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')
print('antepenúltima:n', _iii, 'nn')
print('penúltima:n', _ii, 'nn')
print('última:n', _i, 'nn')
print('El input de la celda 11 es:')
print(_i11)
print('Te he engañado. No existe output para la celda 11')
print('Si intentas acceder al valor _11 obtendrás un NameError ya que no existe la variable')
print('pero te puedo enseñar el de la celda 7:')
print(_7)
In[11]
'for celda in In:n    print(celda)'
_ih[11]
'for celda in In:n    print(celda)'
%hist 1-10
%hist 1-10 12 14
%hist -g a = 1
%hist -o

Otra cosa interesante es la opción -p, que coloca un prompt delante de cada línea de la historia que se muestra. Esto puede ser útil para, por ejemplo, escribir doctests.

En el siguiente ejemplo vamos a usar la opción -p junto con la opción -o:

%hist -po 1-10
>>> from IPython.utils import path
... path.locate_profile()
...
'/home/kiko/.config/ipython/profile_default'
>>> get_ipython().history_manager.hist_file
'/home/kiko/.config/ipython/profile_default/history.sqlite'
>>> In?
>>> a = 2
>>> print(a)
>>> Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite'}
>>> a
2
>>> Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite',
 7: 2}
>>> b = Out[7]
>>> print(b)

Si queremos guardar la historia o parte de la historia en un fichero para, por ejemplo, los doctests, podemos usar la opción -f.

Con la siguiente línea de código vamos a guardar el input, el output y vamos a colocar la línea del prompt de las 10 primeras celdas en un fichero llamado kk.txt:

%hist 1-10 -pof kk.txt

Si queremos acceder a la historia de una sesión anterior podemos usar lo siguiente:

%hist ~1/1-10
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)

De esta forma accederemos a las 10 primeras líneas de la sesión anterior. Si queremos acceder a las 10 primeras líneas de la penúltima sesión podemos hacer:

%hist ~2/1-10
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[9]

Si, además, queréis numerar las celdas usadas podéis usar la opción -n:

%hist ~2/1-10 -n
213/1:
from IPython.utils import path
path.locate_profile()
213/2: get_ipython().history_manager.hist_file
213/3: In?
213/4: a = 2
213/5: print(a)
213/6: Out
213/7: a
213/8: Out
213/9: b = Out[9]

Algunos de los comandos usados no son aceptados por un intérprete Python cualquiera, como por ejemplo los comandos mágicos que empiezan por %. Por ello, podemos obtener los comandos ya traducidos a código Python ejecutable usando la opción -t de la historia:

%hist 1-10 -t
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
get_ipython().magic('pinfo In')
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)

En la tercera línea podéis ver que en lugar de escribir %pinfo In ha escrito get_ipython().magic('pinfo In').

Acceso a la historia de los directorios usados

_dh (también podemos usar %dhist) nos da información de los directorios recorridos. Por ejemplo, voy a recorrer varios directorios y después veremos la historia de los directorios recorridos:

cd /home/kiko/pyprojs
/home/kiko/pyprojs
pwd
'/home/kiko/pyprojs'
cd /home/kiko/pyprojs/ipython-master/nb/
/home/kiko/pyprojs/ipython-master/nb

Si ahora escribimos:

%dhist
Directory history (kept in _dh)
0: /home/kiko/pyprojs/ipython-master/nb
1: /home/kiko/pyprojs
2: /home/kiko/pyprojs/ipython-master/nb

O algo, más o menos, equivalente:

_dh

En este caso nos devuelve una lista:

['/home/kiko/pyprojs/ipython-master/nb',
 '/home/kiko/pyprojs',
 '/home/kiko/pyprojs/ipython-master/nb']

Si solo quiero saber el directorio del que partí en la sesión de IPython en la que me encuentro puedo hacer lo siguiente:

_dh[0]

Y obtengo:

'/home/kiko/pyprojs/ipython-master/nb'

Y esto es todo de momento. Podéis combinar muchas cosas de las vistas aquí con cosas como %macro, %edit, %pastebin,... Si da tiempo, algo muy caro últimamente, hablaremos sobre algunas cosas que se me ocurren en próximas entregas.

Saludos y hasta la próxima entrega.

P.D.: Si veis alguna errata podéis usar los comentarios o mandar algún commit al repositorio de los notebooks.

157 cosas de IPython que no sabías y nunca preguntaste (I)

Voy a inaugurar una nueva serie de entradas dedicadas a IPython hablando de cosas que no son tan evidentes o que la gente no suele usar pero que están ahí y son tremendamente útiles una vez que las conoces y las incluyes en tu flujo de trabajo.

Inicialmente se iba a llamar '305928 cosas sobre IPython que no sabías (o sí) y que nunca tuviste tiempo de preguntar o de leer en la documentación o de leer en el código o nunca te hicieron falta o te estabas tomando unas cañas o estabas haciendo otras cosas más seductoras e inconfesables o {0}' pero me pareció un título un poco largo. Finalmente lo he reducido a 157 y pico, más o menos, para vuestra salud mental y la mia. El nombre está inspirado de forma muy libre en el nombre de la charla dada por Victor Terrón en la PyConES 2013 (¿que no la has visto? venga, a hacer los deberes, rápido. Y después vuelve por aquí).

Primero de todo, [mode taliban ON] se escribe IPython, las dos primeras en mayúscula y no iPython o ipython o aipaizon o IPhyton (sic) [mode taliban OFF]. Por tanto, recordadlo si no queréis que la vena de la frente de Mathias Bussonier se hinche.

Todos sabéis lo que es IPhyton (:-)), si no es así le puedes echar un vistazo a la documentación que está en la página oficial o visitar este vídeo que preparó JuanLu sobre el notebook. Si, aun así, sois tan vagos como yo y no queréis ver nada de lo anterior os cuento, como brevísimo resumen, que IPython es una consola interactiva con super poderes y magia negra incluida.

Ayuda

Ayuda estándar de IPython

Vamos a empezar con cosas muy sencillas como el uso de la ayuda que ofrece IPython. Normalmente, para obtener ayuda de un objeto se usa el comando help(), en IPython se puede usar la ayuda usando símbolos de interrogación. Si se usa un solo símbolo de interrogación se obtiene información general del objeto mientras que si se usan dos símbolos de interrogación se puede acceder a la implementación misma del objeto (solo en el caso de que haya sido programado en Python). Por ejemplo, veamos la ayuda del objeto calendar dentro de la biblioteca calendar disponible en la librería estándar y programada en Python.

from calendar import calendar
?calendar

Nos daría la siguiente información (os saltará una ventana en la parte inferior del navegador):

Type:       method
String Form:<bound method TextCalendar.formatyear of <calendar.TextCalendar object at 0xb6b6082c>>
File:       /usr/local/lib/python3.3/calendar.py
Definition: calendar(self, theyear, w=2, l=1, c=6, m=3)
Docstring:  Returns a year's calendar as a multi-line string.
Class Docstring:
method(function, instance)
Create a bound instance method object.

[Nota] El comando anterior es equivalente a hacer calendar?,
y también es equivalente a usar %pinfo calendar.

Si ahora usamos el doble signo de interrogación obtendremos información mucho más detallada como el fichero en el que se encuentra la función y el código usado para implementarla, entre otras cosas:

??calendar

La siguiente información saldrá en una ventana en la parte inferior del navegador

Type:       method
String Form:<bound method TextCalendar.formatyear of <calendar.TextCalendar object at 0xb6b6082c>>
File:       /usr/local/lib/python3.3/calendar.py
Definition: calendar(self, theyear, w=2, l=1, c=6, m=3)
Source:
    def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                 for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('n' * l)
        return ''.join(v)
Class Docstring:
method(function, instance)
Create a bound instance method object.

[Nota] El comando anterior es equivalente a hacer calendar??
y también es equivalente a usar %pinfo2 calendar.

Si ahora usamos la ayuda usual disponible, función help() de la siguiente forma help(calendar), veremos que la información es bastante más escueta que la obtenida mediante IPython.

help(calendar)

El resultado será:

Help on method formatyear in module calendar:
formatyear(self, theyear, w=2, l=1, c=6, m=3) method of calendar.TextCalendar instance
Returns a year's calendar as a multi-line string.

Uso de wildcards o comodines

Con el signo de interrogación también podemos usar wildcards para obtener todos los objetos que cumplen el criterio. Por ejemplo, nos acordamos que el otro día usamos una función que empezaba por ca pero no nos acordamos del nombre completo. Podemos buscar todos los objetos que se encuentran en el namespace de la siguiente forma:

ca*?

Y IPython nos dará lo siguiente:

calendar
callable

Esto no es excesivamente útil ya que IPython ofrece autocompletado con la tecla de tabulación y llegaríamos al mismo resultado de forma sencilla. Pero, ¿y si nos acordamos que el objeto usado terminaba por ar en lugar de empezar por ca? En este caso, el autocompletado no nos resultaría de mucha ayuda. Pero podríamos usar lo siguiente:

*ar?

Lo anterior nos daría lo siguiente:

calendar

Pero no, no era lo que buscaba. En realidad estaba buscando un objeto que contenía ar. Lo podemos buscar de la siguiente forma.

*ar*?

Lo anterior nos daría lo siguiente:

BytesWarning
DeprecationWarning
FutureWarning
ImportWarning
KeyboardInterrupt
PendingDeprecationWarning
ResourceWarning
RuntimeWarning
SyntaxWarning
UnicodeWarning
UserWarning
Warning
bytearray
calendar
vars

Algunas funciones mágicas de ayuda

También le podéis echar un ojo a %pdef, %pdoc, %psource o %pfile para obtener información de diverso tipo sobre el objeto de turno.

Creación de nuestras propias funciones mágicas de ayuda

La ayuda del notebook sale en una zona inferior de la ventana del navegador. En general me resulta más incómoda que si se imprimiese como un ouput estándar dentro del navegador mismo. Con la siguiente receta, un poco modificada por mí, podemos hacer que determinada información salga inline:

Receta hecha por jiffyclub

## Importamos distintas funciones del módulo oinspect de IPython.
## Muchas de estas funciones son un wrapper sobre las funciones del
##   módulo inspect de la stdlib.
from IPython.core.oinspect import (getsource, getdoc,
                                   find_file, find_source_lines)
## Importamos lo siguiente para mostrar la información en pantalla
from IPython.display import display, HTML
## Importamos lo siguiente para convertir nuestra ayuda a magig functions
from IPython.core.magic import Magics, magics_class, line_magic
## Los siguientes imports serán usados para resaltar la sintáxis del código
##   fuente.
## Es necesario tener instalada la librería pygments.
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
## Llamamos a la InteractiveShell y obtenemos el namespace
##   del usuario (user_ns) que es un diccionario con los
##   objetos disponibles
ip = get_ipython()
my_ns = ip.user_ns
@magics_class
class KikoMagic(Magics):
    def __init__(self, shell):
        super(KikoMagic, self).__init__(shell)
    @line_magic
    def kdoc(self, obj):
        """
        Retrieve the info of an object and display this info in an output.
        """
        if obj in my_ns.keys():
            print("Doc info for {}:n".format(obj))
            print(getdoc(my_ns[obj]))
        else:
            print("There is no info for {}".format(obj))
    @line_magic
    def kfile(self, obj):
        """
        Retrieve the file where the object is implemented.
        """
        if obj in my_ns.keys():
            print("{} implemented in file:n".format(obj))
            print(find_file(my_ns[obj]))
        else:
            print("We can't not find the file for {}".format(obj))
    @line_magic
    def ksourceline(self, obj):
        """
        Retrieve the first line in the source file where
        the object is implemented.
        """
        if obj in my_ns.keys():
            print("The implementation of {}".format(obj))
            print("starts at line {}".format(find_source_lines(my_ns[obj])))
            print("in file {}".format(find_file(my_ns[obj])))
        else:
            print("We can't not find the file for {}".format(obj))
    @line_magic
    def ksource(self, obj):
        """
        Retrieve the info and the source of an object and
        display the info in an output.
        """
        formatter = HtmlFormatter(linenos=False, cssclass="source", nobackground=True)
        template = """</pre>
<style><!--
{}
--></style>
<pre>{}"""
 src = getsource(my_ns[obj])
 html = highlight(src, PythonLexer(), formatter)
 css = formatter.get_style_defs()
 display(HTML(template.format(css,html)))
 @line_magic
 def khelp(self, obj):
 self.kdoc(obj)
 print("")
 self.ksourceline(obj)
 print("")
 self.ksource(obj)
## Registramos las nuevas funciones mágicas para que estén
## disponibles en el nb.
ip.register_magics(KikoMagic)

Dos de las funciones mágicas solo funcionarán en el notebook y no en la consola de IPython ya que estamos haciendo uso de display(HTML(...)) y la qtconsole, por ejemplo, no es capaz de mostrar el código html de forma correcta. Ahora usamos las nuevas funciones mágicas que acabamos de crear:

  • kdoc
%kdoc calendar

Que nos devolverá:

Doc info for calendar:
Returns a year's calendar as a multi-line string.
  • kfile
%kfile calendar

Que nos devolverá:

calendar implemented in file: /usr/local/lib/python3.3/calendar.py
  • ksourceline
%ksourceline calendar

que nos devolverá:

The implementation of calendar starts at line 334 in file /usr/local/lib/python3.3/calendar.py
  • ksource
%ksource calendar

que nos devolverá:

def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('n' * l)
        return ''.join(v)
  • khelp
%khelp calendar

Que nos devolverá:

Doc info for calendar:
Returns a year's calendar as a multi-line string.
The implementation of calendar
starts at line 334
in file /usr/local/lib/python3.3/calendar.py
    def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('n' * l)
        return ''.join(v)

¡¡Eyyy, qué chulo!! Acabamos de personalizar un poco el notebook de IPython de forma muy sencilla.

Si solo queremos hacer una función mágica lo podemos hacer de forma un poco más sencilla que la vista anteriormente. En el siguiente código se muestra cómo (receta hecha por Brian Granger y que he actualizado a las últimas versiones de IPython para hacerla funcionar):

from IPython.core.oinspect import getdoc
ip = get_ipython()
my_ns = ip.user_ns
## Definimos la función que hace lo que queremos. La siguiente
##   función hace lo mismo que la función mágica kdoc que hemos
##   implementado anteriormente
def new_magic(obj):
    if obj in my_ns.keys():
        print(getdoc(my_ns[obj]))
    else:
        print("No info found for {}".format(obj))
## En la siguiente línea definimos la anterior función como
##   una 'line magic' que se llamará 'my_new_magic'
ip.register_magic_function(new_magic, 'line', "my_new_magic")

Ahora la vamos a hacer funcionar:

%my_new_magic calendar

que nos devolverá:

Returns a year's calendar as a multi-line string.

Resumen

Hemos visto como:

  • usar la ayuda de IPython, más avanzada que el uso de la función built-in help(),
  • uso de comodines (wildcards),
  • las funciones mágicas útiles para obtener ayuda de Python,
  • como crear nuestras propias funciones mágicas de ayuda (o de lo que queráis).

Si se os ocurre algo para completar esta información podéis hacer un pull request a nuestro repo de notebooks y actualizaremos la información.

Entrevista a Fernando Pérez, creador de IPython

Hoy os traemos una novedad desde Pybonacci: inauguramos la sección de entrevistas con Fernando Pérez, que ha accedido a charlar un rato con nosotros sobre el pasado, presente y futuro de IPython, la ciencia abierta y alguna que otra cosa más.

Enlaces

  • Web de IPython http://ipython.org/
  • Beca de la Fundación Sloan http://ipython.org/sloan-grant.html
  • Etiqueta ipython en Stack Overflow http://stackoverflow.com/tags/ipython
  • Lista de correo ipython-dev http://mail.scipy.org/mailman/listinfo/ipython-dev
  • Información para desarrolladores https://github.com/ipython/ipython/wiki/Dev:-Index
  • Chat de desarrolladores http://www.hipchat.com/ghtNzvmfC

Repaso a PyData 2013

Unos días después de la PyConUS 2013 se celebró la primera PyData del año (creo que serán semestrales de forma regular aunque el tiempo dirá). Entre las charlas había algunas introductorias, otras más avanzadas y otras enseñando aplicaciones prácticas.

Entre las charlas introductorias destacaremos:

Introducción a Numpy por Bryan Van De Ven: si no conoces absolutamente nada de Numpy esta es tu charla. Da un repaso por las cosas más frecuentes del uso de Numpy sin meterse en cosas muy esotéricas. Puedes sacar la libreta e ir apuntando las cosas que creas que te puedan resultar útiles para tus análisis.

Pandas por Wes McKinney: Es una charla introductoria. El problema que veo es que Pandas no es algo tan centrado como Numpy con su ndarray, las posibilidades de uso son múltiples y, quizá, hacer algo introductorio en vídeo sobre Pandas no resulte tan sencillo como  hacerlo con Numpy. En general, la documentación de Pandas es aceptable (aunque incompleta en algunos momentos) y la veo como un buen punto de partida antes de empezar a ver vídeos sobre Pandas. Creo que lo mejor para empezar con Pandas es echarle un ojo al tour de 10 minutos en vídeo o en texto) y luego empezar a trastear con la librería y con la documentación para empezar a entenderlo. Por nuestra parte, estamos preparando nuestro tutorial cuyos primeros capítulos estaran disponibles en breve, stay tuned!!!! En esta conferencia ha habido más vídeos sobre Pandas pero son más avanzados (primero para marujear con datos de forma productiva, segundo (con numpy y statsmodels) para análisis de series temporales) .

Aprendiendo Python por Peter Norvig: Otro tutorial más para empezar con Python!!

Hacer bonitos gráficos con MatPlotLib por Mike Müller: Otro más avanzado muestra como hacer MatPlotLib más interactivo gracias al gran Jake Vanderplas. Os dejamos aquí nuestro tutorial de matplotlib por si alguno no lo conoce aún (#autobombo).

Visualización de datos con NodeBox por Lynn Cherny: Librería para hacer gráficos más 'artísticos'. Yo tengo sentimientos encontrados con algunos enfoques de este tipo de gráficos (NodeBox, D3,...) por lo que te recomiendo mejor verlo y, si alguien quiere, lo discutimos en los comentarios.

Scikit-image por Davin Potts: Creo que esta librería es una de las grandes desconocidas y ofrece unas posibilidades muy interesantes. Si no la conoces deberías echarle un ojo al vídeo.

Entre las que hablan sobre cosas más prácticas y no específicamente de librerías destacaría (alguna no porque me haya gustado especialmente):

Análisis de redes sociales por Katherine Chuang: Estas están muy de moda (teoría de grafos) y están empezando a ser aburridas si no muestran algo excepcional o no sacan conclusiones **medibles** de todo el análisis chachiguay que hacen. Usa NetworkX, también muy de moda.

Plataforma de datos espacio temporales para el océano por André Karpistsenko: Esta me ha parecido interesante ya que muestra todo el pifostio de tecnologías y trabajo que hay detrás de muchas webs a las que voy a descargarme datos para mis análisis.

Hay más charlas avanzadas que hablan de HDF5 ([1]), Machine Learning ([1], [2], [3]), Blaze (el futuro de Numpy), IPython y más cosas del Big Data y herramientas Python para lidiar con ello.

Si le echáis un ojo a algún vídeo, por favor, dejad algún comentario más abajo para saber lo que os ha parecido.

Saludos y espero veros pronto entre esa gran cantidad de datos 😛