Capítulo 1. Introducción Introducción

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

"¡Data! ¡Data! Data!", gritó impaciente. "No puedo hacer ladrillos sin arcilla".

Arthur Conan Doyle

El ascenso de los datos

En vivimos en un mundo ahogado en datos. Los sitios web rastrean cada clic del usuario. Tu teléfono inteligente está creando un registro de tu ubicación y velocidad cada segundo de cada día. Los "cuantificadores de sí mismos" llevan podómetros en esteroides que registran constantemente su frecuencia cardiaca, hábitos de movimiento, dieta y patrones de sueño. Los coches inteligentes recopilan hábitos de conducción, las casas inteligentes recopilan hábitos de vida y los vendedores inteligentes recopilan hábitos de compra. La propia Internet representa un enorme gráfico de conocimiento que contiene (entre otras cosas) una enorme enciclopedia de referencias cruzadas; bases de datos de dominios específicos sobre películas, música, resultados deportivos, máquinas de pinball, memes y cócteles; y demasiadas estadísticas gubernamentales (¡algunas de ellas casi ciertas!) de demasiados gobiernos como para que te hagas a la idea.

Enterradas en estos datos hay respuestas a innumerables preguntas que a nadie se le ha ocurrido formular. En este libro aprenderemos a encontrarlas.

¿Qué es la Ciencia de Datos?

Hay un chiste en que dice que un científico de datos es alguien que sabe más estadística que un informático y más informática que un estadístico. (No he dicho que sea un buen chiste.) De hecho, algunos científicos de datos son -a efectos prácticos- estadísticos, mientras que otros son bastante indistinguibles de los ingenieros de software. Algunos son expertos en aprendizaje automático, mientras que otros no podrían aprenderlo ni en la guardería. Algunos son doctores con un impresionante historial de publicaciones, mientras que otros no han leído nunca un artículo académico (aunque debería darles vergüenza). En resumen, no importa cómo definas la ciencia de datos, encontrarás profesionales para los que la definición es total y absolutamente errónea.

Sin embargo, no dejaremos que eso nos impida intentarlo. Diremos que un científico de datos es alguien que extrae ideas de datos desordenados. El mundo actual está lleno de personas que intentan convertir los datos en información.

Por ejemplo, el sitio de citas OkCupid pide a sus miembros que respondan a miles de preguntas para encontrar las parejas más adecuadas para ellos. Pero también analiza estos resultados para descubrir preguntas inocuas que puedes hacer a alguien para averiguar la probabilidad de que se acueste contigo en la primera cita.

Facebook te pide que indiques tu ciudad natal y tu ubicación actual, aparentemente para facilitar que tus amigos te encuentren y conecten contigo. Pero también analiza estas ubicaciones para identificar patrones migratorios globales y dónde viven las aficiones de los distintos equipos de fútbol.

Como gran minorista, Target hace un seguimiento de tus compras e interacciones, tanto en Internet como en la tienda. Y utiliza los datos para predecir cuáles de sus clientas están embarazadas, con el fin de venderles mejor las compras relacionadas con el bebé.

En 2012, la campaña de Obama empleó a docenas de científicos de datos que analizaron y experimentaron para identificar a los votantes que necesitaban más atención, elegir los programas y llamamientos de recaudación de fondos óptimos para cada donante y centrar los esfuerzos de captación de votos allí donde tenían más probabilidades de ser útiles. Y en 2016, la campaña de Trump probó una asombrosa variedad de anuncios online y analizó los datos para averiguar qué funcionaba y qué no.

Ahora bien, antes de que empieces a sentirte demasiado hastiado: algunos científicos de datos también utilizan ocasionalmente sus habilidades para el bien -utilizandodatos para hacer que el gobierno sea más eficaz, para ayudar a los sin techo y para mejorar la salud pública-. Pero, desde luego, no te perjudicará en tu carrera si te gusta averiguar la mejor forma de hacer que la gente haga clic en los anuncios.

Hipótesis motivadora: DataSciencester

¡Enhorabuena! Acabas de ser contratado para dirigir los esfuerzos de ciencia de datos en DataSciencester, la red social para científicos de datos.

Nota

Cuando escribí la primera edición de este libro, pensé que "una red social para científicos de datos" era una hipótesis divertida y tonta. Desde entonces, la gente ha creado realmente redes sociales para científicos de datos, y han recaudado mucho más dinero de los capitalistas de riesgo que el que yo gané con mi libro. Lo más probable es que aquí haya una valiosa lección sobre las hipótesis tontas de la ciencia de datos y/o la publicación de libros.

A pesar de ser para científicos de datos, DataSciencester nunca ha invertido realmente en construir su propia práctica de ciencia de datos. (Para ser justos, DataSciencester tampoco ha invertido nunca en construir su producto). ¡Ese será tu trabajo! A lo largo del libro, aprenderemos conceptos de ciencia de datos resolviendo problemas con los que te encuentres en el trabajo. A veces analizaremos datos proporcionados explícitamente por los usuarios, a veces analizaremos datos generados a través de sus interacciones con el sitio, y a veces incluso analizaremos datos de experimentos que diseñaremos.

Y como DataSciencester tiene una fuerte mentalidad de "no se ha inventado aquí", construiremos nuestras propias herramientas desde cero. Al final, tendrás una comprensión bastante sólida de los fundamentos de la ciencia de datos. Y estarás preparado para aplicar tus habilidades en una empresa con una premisa menos inestable, o a cualquier otro problema que te interese.

Bienvenido a bordo y ¡buena suerte! (Puedes llevar vaqueros los viernes, y el baño está al final del pasillo, a la derecha).

Encontrar conectores clave

Es tu primer día de trabajo en DataSciencester, y el vicepresidente de Redes está lleno de preguntas sobre tus usuarios. Hasta ahora no tenía a nadie a quien preguntar, así que está muy contento de tenerte a bordo.

En concreto, quiere que identifiques quiénes son los "conectores clave" entre los científicos de datos. Para ello, te da un volcado de toda la red DataSciencester. (En la vida real, la gente no suele darte los datos que necesitas. El Capítulo 9 está dedicado a la obtención de datos).

¿Qué aspecto tiene este volcado de datos? Consiste en una lista de usuarios, cada uno representado por un dict que contiene el id (que es un número) y el name (que, en una de las grandes coincidencias cósmicas, rima con el id del usuario) de ese usuario:

users = [
    { "id": 0, "name": "Hero" },
    { "id": 1, "name": "Dunn" },
    { "id": 2, "name": "Sue" },
    { "id": 3, "name": "Chi" },
    { "id": 4, "name": "Thor" },
    { "id": 5, "name": "Clive" },
    { "id": 6, "name": "Hicks" },
    { "id": 7, "name": "Devin" },
    { "id": 8, "name": "Kate" },
    { "id": 9, "name": "Klein" }
]

También te da los datos de "amistad", representados como una lista de pares de ID:

friendship_pairs = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 4),
                    (4, 5), (5, 6), (5, 7), (6, 8), (7, 8), (8, 9)]

Por ejemplo, la tupla (0, 1) indica que el científico de datos con id 0 (Héroe) y el científico de datos con id 1 (Dunn) son amigos. La red se ilustra en la Figura 1-1.

The DataSciencester network.
Figura 1-1. La red DataSciencester

Tener las amistades representadas como una lista de pares no es la forma más fácil de trabajar con ellas. Para encontrar todas las amistades del usuario 1, tienes que iterar sobre cada par buscando los pares que contengan 1. Si tuvieras muchos pares, esto llevaría mucho tiempo.

En su lugar, vamos a crear un dict en el que las claves sean usuarios ids y los valores sean listas de amigos ids. (Buscar cosas en un dict es muy rápido.)

Nota

No te obsesiones demasiado con los detalles del código ahora mismo. En el Capítulo 2, te daré un curso intensivo de Python. De momento, intenta captar el sabor general de lo que estamos haciendo.

Seguiremos teniendo que buscar en cada par para crear el dict, pero sólo tendremos que hacerlo una vez, y después obtendremos búsquedas baratas:

# Initialize the dict with an empty list for each user id:
friendships = {user["id"]: [] for user in users}

# And loop over the friendship pairs to populate it:
for i, j in friendship_pairs:
    friendships[i].append(j)  # Add j as a friend of user i
    friendships[j].append(i)  # Add i as a friend of user j

Ahora que tenemos las amistades en un dict, podemos hacer preguntas fácilmente a nuestro gráfico, como "¿Cuál es el número medio de conexiones?".

Primero hallamos el número total de conexiones, sumando las longitudes de todas las listas friends:

def number_of_friends(user):
    """How many friends does _user_ have?"""
    user_id = user["id"]
    friend_ids = friendships[user_id]
    return len(friend_ids)

total_connections = sum(number_of_friends(user)
                        for user in users)        # 24

Y luego simplemente dividimos por el número de usuarios:

num_users = len(users)                            # length of the users list
avg_connections = total_connections / num_users   # 24 / 10 == 2.4

También es fácil encontrar a las personas más conectadas: son las que tienen el mayor número de amigos.

Como no hay muchos usuarios, podemos ordenarlos simplemente de "más amigos" a "menos amigos":

# Create a list (user_id, number_of_friends).
num_friends_by_id = [(user["id"], number_of_friends(user))
                     for user in users]

num_friends_by_id.sort(                                # Sort the list
       key=lambda id_and_friends: id_and_friends[1],   # by num_friends
       reverse=True)                                   # largest to smallest

# Each pair is (user_id, num_friends):
# [(1, 3), (2, 3), (3, 3), (5, 3), (8, 3),
#  (0, 2), (4, 2), (6, 2), (7, 2), (9, 1)]

Una forma de pensar en lo que hemos hecho es como una forma de identificar a las personas que de alguna manera son centrales en la red. De hecho, lo que acabamos de calcular es la métrica de red centralidad de grado(Figura 1-2).

The DataSciencester network sized by degree.
Figura 1-2. La red DataSciencester dimensionada por grados

Esto tiene la virtud de ser bastante fácil de calcular, pero no siempre da los resultados que desearías o esperarías. Por ejemplo, en la red DataSciencester Thor (id 4) sólo tiene dos conexiones, mientras que Dunn (id 1) tiene tres. Sin embargo, cuando observamos la red, intuitivamente parece que Thor debería ser más central. En el Capítulo 22, investigaremos las redes con más detalle, y estudiaremos nociones más complejas de centralidad que pueden o no concordar mejor con nuestra intuición.

Científicos de datos que quizá conozcas

Mientras sigues rellenando el papeleo de los nuevos contratados, la Vicepresidenta de Fraternización pasa por tu mesa. Quiere fomentar más conexiones entre tus miembros, y te pide que diseñes un sugeridor de "Científicos de datos que quizá conozcas".

Tu primer instinto es sugerir que los usuarios podrían conocer a los amigos de sus amigos. Así que escribes un código para iterar sobre sus amigos y recopilar los amigos de los amigos:

def foaf_ids_bad(user):
    """foaf is short for "friend of a friend" """
    return [foaf_id
            for friend_id in friendships[user["id"]]
            for foaf_id in friendships[friend_id]]

Cuando llamamos a esto en users[0] (Héroe), se produce:

[0, 2, 3, 0, 1, 3]

Incluye al usuario 0 dos veces, ya que Hero es amigo de sus dos amigos. Incluye a los usuarios 1 y 2, aunque ambos ya son amigos de Hero. E incluye al usuario 3 dos veces, ya que Chi es accesible a través de dos amigos diferentes:

print(friendships[0])  # [1, 2]
print(friendships[1])  # [0, 2, 3]
print(friendships[2])  # [0, 1, 3]

Saber que la gente es amiga de sus amigos de múltiples maneras parece una información interesante, así que quizá en su lugar deberíamos producir un recuento de amigos comunes. Y probablemente deberíamos excluir a las personas ya conocidas por el usuario:

from collections import Counter                   # not loaded by default

def friends_of_friends(user):
    user_id = user["id"]
    return Counter(
        foaf_id
        for friend_id in friendships[user_id]     # For each of my friends,
        for foaf_id in friendships[friend_id]     # find their friends
        if foaf_id != user_id                     # who aren't me
        and foaf_id not in friendships[user_id]   # and aren't my friends.
    )


print(friends_of_friends(users[3]))               # Counter({0: 2, 5: 1})

Esto indica correctamente a Chi (id 3) que tiene dos amigos comunes con Hero (id 0) pero sólo un amigo común con Clive (id 5).

Como científico de datos, sabes que también podrías disfrutar conociendo a usuarios con intereses similares. (Este es un buen ejemplo del aspecto de "experiencia sustantiva" de la ciencia de datos). Tras preguntar por ahí, consigues hacerte con estos datos, en forma de lista de parejas (user_id, interest):

interests = [
    (0, "Hadoop"), (0, "Big Data"), (0, "HBase"), (0, "Java"),
    (0, "Spark"), (0, "Storm"), (0, "Cassandra"),
    (1, "NoSQL"), (1, "MongoDB"), (1, "Cassandra"), (1, "HBase"),
    (1, "Postgres"), (2, "Python"), (2, "scikit-learn"), (2, "scipy"),
    (2, "numpy"), (2, "statsmodels"), (2, "pandas"), (3, "R"), (3, "Python"),
    (3, "statistics"), (3, "regression"), (3, "probability"),
    (4, "machine learning"), (4, "regression"), (4, "decision trees"),
    (4, "libsvm"), (5, "Python"), (5, "R"), (5, "Java"), (5, "C++"),
    (5, "Haskell"), (5, "programming languages"), (6, "statistics"),
    (6, "probability"), (6, "mathematics"), (6, "theory"),
    (7, "machine learning"), (7, "scikit-learn"), (7, "Mahout"),
    (7, "neural networks"), (8, "neural networks"), (8, "deep learning"),
    (8, "Big Data"), (8, "artificial intelligence"), (9, "Hadoop"),
    (9, "Java"), (9, "MapReduce"), (9, "Big Data")
]

Por ejemplo, Hero (id 0) no tiene amigos en común con Klein (id 9), pero comparten intereses en Java y los grandes datos.

Es fácil crear una función que encuentre usuarios con un determinado interés:

def data_scientists_who_like(target_interest):
    """Find the ids of all users who like the target interest."""
    return [user_id
            for user_id, user_interest in interests
            if user_interest == target_interest]

Esto funciona, pero tiene que examinar toda la lista de intereses para cada búsqueda. Si tenemos muchos usuarios e intereses (o si simplemente queremos hacer muchas búsquedas), probablemente sea mejor que construyamos un índice de intereses a usuarios:

from collections import defaultdict

# Keys are interests, values are lists of user_ids with that interest
user_ids_by_interest = defaultdict(list)

for user_id, interest in interests:
    user_ids_by_interest[interest].append(user_id)

Y otra de usuarios a intereses:

# Keys are user_ids, values are lists of interests for that user_id.
interests_by_user_id = defaultdict(list)

for user_id, interest in interests:
    interests_by_user_id[user_id].append(interest)

Ahora es fácil encontrar quién tiene más intereses en común con un usuario determinado:

  • Iterar sobre los intereses del usuario.

  • Para cada interés, itera sobre los demás usuarios con ese interés.

  • Lleva la cuenta de las veces que nos vemos usuario.

En clave:

def most_common_interests_with(user):
    return Counter(
        interested_user_id
        for interest in interests_by_user_id[user["id"]]
        for interested_user_id in user_ids_by_interest[interest]
        if interested_user_id != user["id"]
    )

Podríamos utilizarlo para crear una función más rica de "Científicos de datos que quizá conozcas", basada en una combinación de amigos comunes e intereses mutuos. Exploraremos este tipo de aplicaciones en el Capítulo 23.

Salarios y experiencia

Justo cuando estás a punto de ir a comer, el vicepresidente de Relaciones Públicas te pregunta si puedes proporcionarle algunos datos divertidos sobre cuánto ganan los científicos de datos. Los datos salariales son, por supuesto, sensibles, pero se las arregla para proporcionarte un conjunto de datos anónimo que contiene el salary de cada usuario (en dólares) y el tenure como científico de datos (en años):

salaries_and_tenures = [(83000, 8.7), (88000, 8.1),
                        (48000, 0.7), (76000, 6),
                        (69000, 6.5), (76000, 7.5),
                        (60000, 2.5), (83000, 10),
                        (48000, 1.9), (63000, 4.2)]

El primer paso natural es trazar los datos (que veremos cómo hacer en el Capítulo 3). Puedes ver los resultados en la Figura 1-3.

Salary by Years Experience.
Figura 1-3. Salario por años de experiencia

Parece claro que las personas con más experiencia suelen ganar más. ¿Cómo puedes convertir esto en un dato divertido? Tu primera idea es fijarte en el salario medio de cada puesto:

# Keys are years, values are lists of the salaries for each tenure.
salary_by_tenure = defaultdict(list)

for salary, tenure in salaries_and_tenures:
    salary_by_tenure[tenure].append(salary)

# Keys are years, each value is average salary for that tenure.
average_salary_by_tenure = {
    tenure: sum(salaries) / len(salaries)
    for tenure, salaries in salary_by_tenure.items()
}

Esto resulta no ser especialmente útil, ya que ninguno de los usuarios tiene la misma antigüedad, lo que significa que sólo informamos de los salarios de los usuarios individuales:

{0.7: 48000.0,
 1.9: 48000.0,
 2.5: 60000.0,
 4.2: 63000.0,
 6: 76000.0,
 6.5: 69000.0,
 7.5: 76000.0,
 8.1: 88000.0,
 8.7: 83000.0,
 10: 83000.0}

Podría ser más útil agrupar las tenencias:

def tenure_bucket(tenure):
    if tenure < 2:
        return "less than two"
    elif tenure < 5:
        return "between two and five"
    else:
        return "more than five"

Luego podemos agrupar los salarios correspondientes a cada cubo:

# Keys are tenure buckets, values are lists of salaries for that bucket.
salary_by_tenure_bucket = defaultdict(list)

for salary, tenure in salaries_and_tenures:
    bucket = tenure_bucket(tenure)
    salary_by_tenure_bucket[bucket].append(salary)

Y, por último, calcula el salario medio de cada grupo:

# Keys are tenure buckets, values are average salary for that bucket.
average_salary_by_bucket = {
  tenure_bucket: sum(salaries) / len(salaries)
  for tenure_bucket, salaries in salary_by_tenure_bucket.items()
}

¿Qué es más interesante?

{'between two and five': 61500.0,
 'less than two': 48000.0,
 'more than five': 79166.66666666667}

Y ya tienes tu frase sonora "¡Los científicos de datos con más de cinco años de experiencia ganan un 65% más que los científicos de datos con poca o ninguna experiencia!"

Pero elegimos los grupos de forma bastante arbitraria. Lo que realmente nos gustaría es hacer alguna afirmación sobre el efecto salarial -de media- de tener un año más de experiencia. Además de ser un dato curioso más rápido, esto nos permite hacer predicciones sobre salarios que no conocemos. Exploraremos esta idea en el Capítulo 14.

Cuentas de pago

Cuando vuelves a tu mesa, el vicepresidente de ingresos te está esperando. Quiere entender mejor qué usuarios pagan las cuentas y cuáles no. (Sabe sus nombres, pero no es una información especialmente procesable).

Observas que parece haber una correspondencia entre los años de experiencia y las cuentas remuneradas:

0.7  paid
1.9  unpaid
2.5  paid
4.2  unpaid
6.0  unpaid
6.5  unpaid
7.5  unpaid
8.1  unpaid
8.7  paid
10.0 paid

Los usuarios con muy pocos y muchos años de experiencia tienden a pagar; los usuarios con una experiencia media, no. En consecuencia, si quisieras crear un modelo -aunque estos datos no son suficientes para basarlo- podrías intentar predecir "de pago" para los usuarios con muy pocos y muchos años de experiencia, y "no de pago" para los usuarios con una experiencia media:

def predict_paid_or_unpaid(years_experience):
  if years_experience < 3.0:
    return "paid"
  elif years_experience < 8.5:
    return "unpaid"
  else:
    return "paid"

Por supuesto, nos fijamos totalmente en los límites.

Con más datos (y más matemáticas), podríamos construir un modelo que predijera la probabilidad de que un usuario pagara en función de sus años de experiencia. Investigaremos este tipo de problema en el Capítulo 16.

Temas de interés

En estás terminando tu primer día, y la Vicepresidenta de Estrategia de Contenidos te pide datos sobre los temas que más interesan a los usuarios, para poder planificar en consecuencia su calendario de blogs. Ya tienes los datos brutos del proyecto del amigo-sugerente:

interests = [
    (0, "Hadoop"), (0, "Big Data"), (0, "HBase"), (0, "Java"),
    (0, "Spark"), (0, "Storm"), (0, "Cassandra"),
    (1, "NoSQL"), (1, "MongoDB"), (1, "Cassandra"), (1, "HBase"),
    (1, "Postgres"), (2, "Python"), (2, "scikit-learn"), (2, "scipy"),
    (2, "numpy"), (2, "statsmodels"), (2, "pandas"), (3, "R"), (3, "Python"),
    (3, "statistics"), (3, "regression"), (3, "probability"),
    (4, "machine learning"), (4, "regression"), (4, "decision trees"),
    (4, "libsvm"), (5, "Python"), (5, "R"), (5, "Java"), (5, "C++"),
    (5, "Haskell"), (5, "programming languages"), (6, "statistics"),
    (6, "probability"), (6, "mathematics"), (6, "theory"),
    (7, "machine learning"), (7, "scikit-learn"), (7, "Mahout"),
    (7, "neural networks"), (8, "neural networks"), (8, "deep learning"),
    (8, "Big Data"), (8, "artificial intelligence"), (9, "Hadoop"),
    (9, "Java"), (9, "MapReduce"), (9, "Big Data")
]

Una forma sencilla (aunque no especialmente emocionante) de encontrar los intereses más populares es contar las palabras:

  1. Pon en minúsculas cada interés (ya que los distintos usuarios pueden o no poner en mayúsculas sus intereses).

  2. Divídelo en palabras.

  3. Cuenta los resultados.

En clave:

words_and_counts = Counter(word
                           for user, interest in interests
                           for word in interest.lower().split())

Esto facilita la enumeración de las palabras que aparecen más de una vez:

for word, count in words_and_counts.most_common():
    if count > 1:
        print(word, count)

que da los resultados que esperas (a menos que esperes que "scikit-learn" se divida en dos palabras, en cuyo caso no da los resultados que esperas):

learning 3
java 3
python 3
big 3
data 3
hbase 2
regression 2
cassandra 2
statistics 2
probability 2
hadoop 2
networks 2
machine 2
neural 2
scikit-learn 2
r 2

Veremos formas más sofisticadas de extraer temas de los datos en el Capítulo 21.

Adelante

Ha sido un primer día exitoso! Exhausto, te escabulles del edificio antes de que nadie pueda pedirte nada más. Descansa bien esta noche, porque mañana es la orientación para nuevos empleados. (Sí, has pasado un día entero de trabajo antes de la orientación para nuevos empleados. Háblalo con RRHH).

Get Ciencia de datos desde cero, 2ª edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.