Sintaxis Común de Python en Ciencia de Datos (Básico)

Estos días he estado leyendo Data Science from Scrach (dirección PDF), un libro de introducción a la ciencia de datos excelente y fácil de comprender. Uno de sus capítulos presenta la sintaxis básica de Python y la sintaxis avanzada comúnmente utilizada en ciencia de datos. Me pareció que la explicación era muy buena, concisa y clara, por lo que la he traducido y la comparto aquí como material de consulta.
Sintaxis de Python comúnmente usada en ciencia de datos (Básica)
Sintaxis de Python comúnmente usada en ciencia de datos (Avanzada)

Este capítulo se centra en presentar la sintaxis y las funcionalidades básicas de Python más útiles para el procesamiento de datos (basado en Python 2.7).

Formato de espacios

Mientras que muchos lenguajes utilizan llaves para controlar los bloques de código, Python opta por la indentación:

for i in [1, 2, 3, 4, 5]:  
    print i          # Primera línea del bucle "for i"  
    for j in [1, 2, 3, 4, 5]:  
        print j      # Primera línea del bucle "for j"  
        print i + j  # Última línea del bucle "for j"  
    print i          # Última línea del bucle "for i"  
print "done looping"  

Esto hace que el código Python sea muy legible, pero también significa que debes prestar atención constante al formato. Los espacios dentro de los paréntesis se ignoran, lo cual es útil al escribir expresiones largas:

long_winded_computation = (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20)  

También facilita la lectura del código:

list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]  
easier_to_read_list_of_lists = [ [1, 2, 3],  
                                 [4 ,5 ,6 ],  
                                 [7 ,8 ,9 ] ]  

Sentencias multilínea

Puedes usar una barra invertida para indicar que dos líneas se conectan (una práctica poco común):

two_plus_three = 2 + \
                 3  

Módulos

Tanto los módulos propios de Python como los de terceros que descargues, deben importarse manualmente para poder utilizarlos.

  1. Simplemente importa el módulo completo directamente:
import re  
my_regex = re.compile("[0-9]+", re.I)  

El módulo re importado aquí se utiliza para expresiones regulares. Una vez importado, puedes usar directamente el nombre del módulo como prefijo (re.) para llamar a funciones específicas.

  1. Si el nombre del módulo que quieres importar ya está en uso en tu código, puedes asignarle otro nombre al importarlo:
import re as regex  
my_regex = regex.compile("[0-9]+", regex.I)  
  1. Si eres “travieso”, podrías importar todo el módulo al espacio de nombres actual, lo que podría sobrescribir accidentalmente variables que ya hayas definido:
match = 10  
from re import *  # El módulo re tiene una función 'match'  
print match       # Imprime la función match  

Como eres una buena persona, confío en que no harás esto.

Operaciones aritméticas

Python 2.7 utiliza la división entera por defecto, por lo que $ 5 / 2 = 2 $. Sin embargo, a menudo no queremos una división entera, así que podemos importar este módulo:

from __future__ import division  

Después de importarlo, obtendremos $5 / 2 = 2.5$.
División entera: $5 // 2 = 2$.

Funciones

Definición de funciones

Una función es una regla que puede recibir cero o más entradas y devolver una salida. En Python, definimos una función con def nombre_funcion(parametros):

def double(x):  
    """Aquí puedes escribir una explicación sobre la funcionalidad de la función  
    Por ejemplo, esta función multiplica la entrada por 2"""  
    # Aquí va el cuerpo de la función, recuerda la indentación  
    return x * 2  

Uso de funciones

En Python, las funciones son objetos de primera clase, lo que significa que podemos asignarlas a variables o pasarlas como argumentos a otras funciones:

def apply_to_one(f):  
    """Llama a la función f pasando 1 como argumento"""  
    return f(1)  
my_double = double          # 'double' apunta a la función definida en la sección anterior  
x = apply_to_one(my_double) # x es igual a 2  

Funciones anónimas

También podemos crear funciones anónimas usando lambda:

y = apply_to_one(lambda x: x + 4)     # Es igual a 5  

Puedes asignar una lambda a otras variables, pero la mayoría de la gente te aconsejará que uses def siempre que sea posible:

another_double = lambda x: 2 * x      # No recomendado  
def another_double(x): return 2 * x   # Práctica recomendada  

Nota adicional:

Paso de parámetros a funciones

Los parámetros de las funciones pueden tener valores predeterminados. Si no se proporcionan argumentos al llamar la función, se usarán los valores predeterminados; si se proporcionan, se pasarán los valores especificados:

def my_print(message="my default message"):  
    print message  
my_print("hello")     # Imprime "hello"  
my_print()            # Imprime "my default message"  

A veces, también es muy útil especificar los argumentos directamente por su nombre:

def subtract(a=0, b=0):  
    return a - b  
subtract(10, 5)   # Devuelve 5  
subtract(0, 5)    # Devuelve -5  
subtract(b=5)     # Lo mismo que el anterior, devuelve -5  

Cadenas de texto (Strings)

Puedes crear cadenas de texto (strings) usando comillas simples o dobles (las comillas deben ir emparejadas):

single_quoted_string = 'data science'  
double_quoted_string = "data science"  

Usa la barra invertida para caracteres de escape, como:

tab_string = "\t"      # Representa el tabulador  
len(tab_string)        # Es igual a 1  

Cuando quieras usar la barra invertida literalmente (para rutas de Windows o expresiones regulares), puedes definirla usando cadenas de texto crudas r"":

not_tab_string = r"\t" # Representa los caracteres '\' y 't'  
len(not_tab_string)    # Es igual a 2  

Crea cadenas de texto multilínea usando tres comillas dobles:

multi_line_string = """Esta es la primera línea  
Esta es la segunda línea  
Esta es la tercera línea"""  

Manejo de excepciones

Cuando un programa encuentra un error, Python lanza una excepción. Si no la manejamos, el programa terminará su ejecución. Para capturar excepciones, podemos usar las sentencias try y except:

try:  
    print 0 / 0  
except ZeroDivisionError:  
    print "No se puede dividir por 0"  

Aunque en otros lenguajes las excepciones pueden verse como algo negativo, en Python, manejarlas activamente puede hacer que tu código sea más conciso y limpio.

Listas

Crear listas

Las listas son colecciones simples y ordenadas, y son una de las estructuras de datos más fundamentales en Python (similares a los arrays en otros lenguajes, pero con algunas características adicionales). Para crear una lista:

integer_list = [1, 2, 3]  
heterogeneous_list = ["string", 0.1, True]  
list_of_lists = [ integer_list, heterogeneous_list, [] ]  
list_length = len(integer_list)   # Es igual a 3  
list_sum = sum(integer_list)      # Es igual a 6  

Acceder a los valores de una lista

Puedes acceder a los valores de una lista mediante el indexado con corchetes:

x = range(10)       # La lista x = [0, 1, ..., 9]  
zero = x[0]         # Es igual a 0, los índices de la lista empiezan en 0  
one = x[1]          # Es igual a 1  
nine = x[-1]        # Es igual a 9, el último elemento de la lista  
eight = x[-2]       # Es igual a 8, el penúltimo elemento de la lista  
x[0] = -1           # La lista actual x = [-1, 1, 2, 3, ..., 9]  

Recortar listas

Puedes recortar (slice) listas con corchetes:

first_three = x[:3]                  # [-1, 1, 2]  
three_to_end = x[3:]                 # [3, 4, ..., 9]  
one_to_four = x[1:5]                 # [1, 2, 3, 4]  
last_three = x[-3:]                  # [7, 8, 9]  
without_first_and_last = x[1:-1]     # [1, 2, ..., 8]  
copy_of_x = x[:]                     # [-1, 1, 2, ..., 9]  

Puedes usar in para verificar si un elemento está en una lista:

1 in [1, 2, 3]        # True  
0 in [1, 2, 3]        # False  

Este método de búsqueda de elementos es ineficiente; úsalo solo si la lista es pequeña o si el tiempo de búsqueda no es crítico.

Concatenar listas

En Python es muy fácil concatenar dos listas:

x = [1, 2, 3]  
x.extend([4, 5, 6])   # Ahora x = [1,2,3,4,5,6]  

Si no quieres modificar la lista original x, puedes usar el operador de suma para crear una nueva lista:

x = [1, 2, 3]  
y = x + [4, 5, 6]     # Ahora y = [1, 2, 3, 4, 5, 6]; x no ha cambiado  

A menudo se usa este método para añadir un elemento a la lista de uno en uno:

x = [1, 2, 3]  
x.append(0)           # Ahora x = [1, 2, 3, 0]  
y = x[-1]             # Es igual a 0  
z = len(x)            # Es igual a 4  

Descomposición de listas

Si sabes cuántos elementos hay en una lista, es muy fácil descomponerla:

x, y = [1, 2]         # Ahora x = 1, y = 2  

Si el número de elementos en ambos lados de la asignación no coincide, obtendrás un ValueError. Por eso, es más común usar un guion bajo para almacenar el resto de la lista:

_, y = [1, 2]         # Ahora y == 2, ignorando el primer elemento  

Tuplas

Las listas y las tuplas son muy similares. La única diferencia es que los elementos de una tupla no pueden ser modificados.

Creación de tuplas

Puedes crear tuplas usando paréntesis o sin ellos:

my_tuple = (1, 2)  
other_tuple = 3, 4  
my_list[1] = 3        # Ahora my_list es [1, 3]  
try:  
    my_tuple[1] = 3  
except TypeError:  
    print "No se puede modificar la tupla"  

Las tuplas son muy convenientes para obtener múltiples valores de retorno de una función:

def sum_and_product(x, y):  
    return (x + y),(x * y)  
sp = sum_and_product(2, 3)    # Es igual a (5, 6)  
s, p = sum_and_product(5, 10) # s = 15, p = 50  

Tanto las tuplas como las listas permiten la asignación múltiple de elementos:

x, y = 1, 2       # Ahora x = 1, y = 2  
x, y = y, x       # Intercambia los valores de dos variables en Python; ahora x = 2, y = 1  

Diccionarios

Creación de diccionarios

Otra estructura de datos fundamental en Python es el diccionario, que te permite obtener rápidamente el valor (value) correspondiente a una clave (key):

empty_dict = {}                       # Definición de diccionario vacío muy 'pythónica'  
empty_dict2 = dict()                  # Definición de diccionario vacío menos 'pythónica'  
grades = { "Joel" : 80, "Tim" : 95 }  # Almacenamiento en diccionario  

Búsqueda de elementos en diccionarios

Puedes usar corchetes y la clave para buscar el valor correspondiente:

joels_grade = grades["Joel"]          # Es igual a 80  

Si la clave que buscas no está en el diccionario, se devolverá un KeyError:

try:  
    kates_grade = grades["Kate"]  
except KeyError:  
    print "¡no hay calificación para Kate!"  

Puedes usar in para verificar si una clave está en el diccionario:

joel_has_grade = "Joel" in grades     # True  
kate_has_grade = "Kate" in grades     # False  

Los diccionarios tienen un método que devuelve un valor predeterminado si la clave buscada no se encuentra (en lugar de lanzar una excepción):

joels_grade = grades.get("Joel", 0)   # Es igual a 80  
kates_grade = grades.get("Kate", 0)   # Es igual a 0  
no_ones_grade = grades.get("No One")  # Devuelve el valor predeterminado None  

Modificación de diccionarios

Puedes usar corchetes para crear o modificar pares clave-valor en un diccionario:

grades["Tim"] = 99                    # Reemplaza el valor antiguo  
grades["Kate"] = 100                  # Añade un par clave-valor  
num_students = len(grades)            # Es igual a 3  

A menudo usaremos diccionarios de esta manera para expresar la estructura de los datos:

tweet = {  
    "user" : "joelgrus",  
    "text" : "Data Science is Awesome",  
    "retweet_count" : 100,  
    "hashtags" : ["#data", "#science", "#datascience", "#awesome", "#yolo"]  
}  

Además de buscar claves específicas, también podemos operar con todas las claves de la siguiente manera:

tweet_keys = tweet.keys()             # Obtiene una lista de claves  
tweet_values = tweet.values()         # Obtiene una lista de valores  
tweet_items = tweet.items()           # Obtiene una tupla (clave, valor)  
"user" in tweet_keys                  # Devuelve True, usa una búsqueda 'in' menos eficiente en la lista  
"user" in tweet                       # Forma más 'pythónica', usa una búsqueda 'in' eficiente en el diccionario  
"joelgrus" in tweet_values            # True  

Las claves en los diccionarios son únicas, y las listas no pueden usarse como claves. Si necesitas una clave compuesta, puedes usar una tupla o convertir la clave a una cadena de texto de alguna manera.

Diccionarios predeterminados (defaultdict)

Si estás intentando contar la frecuencia de cada palabra en un documento, una forma obvia es crear un diccionario donde las palabras sean las claves y sus frecuencias los valores. Luego, iterar por el documento, incrementando el valor de una palabra si ya existe, o añadiendo un nuevo par clave-valor si es la primera vez que aparece:

word_counts = {}  
for word in document:  
    if word in word_counts:  
        word_counts[word] += 1  
    else:  
        word_counts[word] = 1  

Por supuesto, también puedes abordar una clave faltante de forma proactiva, como un ‘ataque preventivo’, de esta manera:

word_counts = {}  
for word in document:  
    try:  
        word_counts[word] += 1  
    except KeyError:  
        word_counts[word] = 1  

El tercer método es usar get, que funciona excelentemente para manejar claves ausentes:

word_counts = {}  
for word in document:  
    previous_count = word_counts.get(word, 0)  
    word_counts[word] = previous_count + 1  

Los diccionarios predeterminados (defaultdict) funcionan como los diccionarios normales, con la única diferencia de que, cuando intentas buscar una clave inexistente, defaultdict crea automáticamente un par clave-valor usando la función que le proporciones. Para usarlo, necesitas importar la biblioteca collections:

from collections import defaultdict  
word_counts = defaultdict(int)        # int() genera 0  
for word in document:  
    word_counts[word] += 1  

Los defaultdict también son muy útiles con listas, diccionarios normales e incluso funciones personalizadas:

dd_list = defaultdict(list)           # list() genera una lista vacía  
dd_list[2].append(1)                  # Ahora dd_list es {2: [1]}  
dd_dict = defaultdict(dict)           # dict() genera un diccionario vacío  
dd_dict["Joel"]["City"] = "Seattle"   # Ahora dd_dict contiene { "Joel" : { "City" : Seattle"}}  
dd_pair = defaultdict(lambda: [0, 0]) # Crea un diccionario donde los valores para las claves son listas  
dd_pair[2][1] = 1                     # Ahora dd_pair contiene {2: [0,1]}  

Este método es muy útil, ya que en el futuro, cuando necesitemos obtener ciertos resultados de clave-valor de un diccionario, no será necesario verificar si la clave existe.

Contador (Counter)

Un contador (Counter) puede transformar directamente un conjunto de valores en un objeto similar a un diccionario, donde las claves son los elementos del conjunto y los valores son el número de veces que aparece cada elemento. Esto se usa con frecuencia al crear histogramas:

from collections import Counter  
c = Counter([0, 1, 2, 0]) # c (aproximadamente) es { 0 : 2, 1 : 1, 2 : 1 }  

De esta manera, tenemos un método muy conveniente para contar la frecuencia de las palabras:

word_counts = Counter(document)  

Los contadores también tienen un método muy útil, most_common, que puede obtener directamente las palabras más frecuentes y sus respectivas frecuencias:

# Imprime las 10 palabras más frecuentes y sus recuentos  
for word, count in word_counts.most_common(10):  
    print word, count  

Conjuntos (Sets)

Otra estructura de datos en Python son los conjuntos (sets), que son colecciones de elementos únicos.
Puedes crear un conjunto y añadirle elementos de esta manera:

s = set()  
s.add(1)          # s es { 1 }  
s.add(2)          # s es { 1, 2 }  
s.add(2)          # s es { 1, 2 }  
x = len(s)        # Es igual a 2  
y = 2 in s        # Es igual a True  
z = 3 in s        # Es igual a False  

Dos grandes razones para usar conjuntos:

Primero, la operación in en los conjuntos es muy eficiente. Cuando la cantidad de elementos en un conjunto de datos es muy grande, buscar elementos en un conjunto es claramente más adecuado que hacerlo en una lista:

stopwords_list = ["a","an","at"] + hundreds_of_other_words + ["yet", "you"]  
"zip" in stopwords_list               # Fallará, necesita verificar cada elemento  
stopwords_set = set(stopwords_list)  
"zip" in stopwords_set                # Búsqueda exitosa y muy rápida  

Segundo, es muy conveniente usar conjuntos para obtener los elementos únicos de un conjunto de datos:

item_list = [1, 2, 3, 1, 2, 3]  
num_items = len(item_list)            # 6  
item_set = set(item_list)             # {1, 2, 3}  
num_distinct_items = len(item_set)    # 3  
distinct_item_list = list(item_set)   # [1, 2, 3]  

Sin embargo, en la práctica, los conjuntos no se utilizan con tanta frecuencia como los diccionarios y las listas.

Sentencias condicionales

En la mayoría de los lenguajes de programación, puedes usar if para las ramas condicionales de esta manera:

if 1 > 2:  
    message = "if only 1 were greater than two…"  
elif 1 > 3:  
    message = "elif stands for 'else if'"  
else:  
    message = "when all else fails use else (if you want to)"  

También puedes escribir sentencias condicionales en una sola línea de esta manera, aunque es poco común:

parity = "even" if x % 2 == 0 else "odd"  

Sentencias de bucle

Bucle while

El bucle while en Python:

x = 0  
while x < 10:  
    print x, "is less than 10"  
    x += 1  

Bucle for

Más comúnmente, se utiliza el bucle for-in:

for x in range(10):  
    print x, "is less than 10"  

Para expresiones lógicas más complejas, se pueden usar las sentencias continue y break:

for x in range(10):  
    if x == 3:  
        continue          # Pasa directamente a la siguiente iteración del bucle  
    if x == 5:  
        break             # Sale completamente del bucle  
    print x  

El resultado será 0, 1, 2 y 4.

Veracidad (Truthiness)

Las variables booleanas en Python funcionan de manera similar a otros lenguajes, con la única diferencia de que la primera letra siempre debe estar en mayúscula:

one_is_less_than_two = 1 < 2      # Es True  
true_equals_false = True == False # Es False  

Python usa None para indicar que un valor no existe, similar a null en otros lenguajes:

x = None  
print x == None        # Imprime True, pero no es lo más elegante  
print x is None        # Imprime True, más elegante  

Python te permite usar otros valores en lugar de booleanos; los siguientes son todos equivalentes a False:

De manera similar, hay muchos valores equivalentes a True, lo que te permite verificar fácilmente listas vacías, cadenas vacías, diccionarios vacíos, etc.

Por supuesto, si no puedes prever el resultado, podrías cometer errores durante el uso:

s = some_function_that_returns_a_string()  
if s:  
    first_char = s[0]  
else:  
    first_char = ""  

Una forma más simple de hacer lo mismo:

first_char = s and s[0]  

Si el primer valor es verdadero, devuelve el segundo valor; de lo contrario, devuelve el primero.

De manera similar, si x puede ser un número o estar vacío, así puedes obtener un x que sea definitivamente un número:

safe_x = x or 0  

Python también tiene la función all, que devuelve True si todos los elementos son True. La función any devuelve True si al menos un elemento es True. Por ejemplo, para una lista donde cada elemento es ‘verdadero’, la función all devolverá True; de lo contrario, devolverá False:

all([True, 1, { 3 }])       # True  
all([True, 1, {}])          # False, {} es equivalente a "False"  
any([True, 1, {}])          # True  
all([])                     # True, no hay ningún elemento equivalente a "False"  
any([])                     # False, no hay ningún elemento equivalente a "True"  

Lectura adicional:
Sintaxis de Python comúnmente usada en ciencia de datos (Avanzada)