Capítulo 01 - Introdução
"Dados! Dados! Dados!" Ele chorou impacientemente. "Não posso fazer tijolos sem barro". (Arthur Conan Doyle)
"Dados! Dados! Dados!" Ele chorou impacientemente. "Não posso fazer tijolos sem barro". (Arthur Conan Doyle)
A ascensão dos dados
Vivemos em um mundo que está se afogando em dados. Os sites rastreiam todos os cliques de todos os usuários. Seu smartphone está construindo um registro da sua localização e velocidade a cada segundo de cada dia. Os "influencers fitness" usam pedômetros que estão sempre gravando seus batimentos cardíacos, hábitos de movimento, dieta e padrões de sono. Os carros inteligentes coletam hábitos de condução, casas inteligentes coletam hábitos de vida e profissionais de marketing inteligentes coletam hábitos de compra. A própria internet representa um enorme gráfico de conhecimento que contém (entre outras coisas) uma enorme enciclopédia cruzada; bancos de dados específicos de domínio sobre filmes, música, resultados esportivos, máquinas de pinball, memes e coquetéis; E muitas estatísticas governamentais (algumas delas quase verdadeiras!) de muitos governos para envolver sua cabeça.
Enterrado nesses dados, são respostas a inúmeras perguntas que ninguém nunca pensou em fazer. Neste estudo aprenderemos a encontrá-los.
O que é ciência de dados?
Há uma piada que diz que um cientista de dados é alguém que conhece mais estatísticas do que um cientista da computação e mais ciência da computação do que um estatístico (eu não disse que era uma boa piada). De fato, alguns cientistas de dados são - para todos os propósitos práticos - estatísticos, enquanto outros são bastante indistinguíveis dos engenheiros de software. Alguns são especialistas em aprendizado de máquina, enquanto outros não conseguiram aprender a sair do jardim de infância. Alguns são doutores com registros impressionantes de publicação, enquanto outros nunca leram um artigo acadêmico. Em suma, praticamente não importa como você define a ciência de dados, você encontrará profissionais para quem a definição é totalmente, absolutamente errada.
No entanto, não vamos deixar que isso nos impeça de tentar. Diremos que um cientista de dados é alguém que extrai insights de dados confusos. O mundo de hoje está cheio de pessoas tentando transformar dados em insights.
Por exemplo, o site de namoro OkCupid pede a seus membros que respondam a milhares de perguntas para encontrar as correspondências mais apropriadas para eles. Mas também analisa esses resultados para descobrir perguntas inócuas que você pode pedir a alguém para descobrir a probabilidade de alguém dormir com você no primeiro encontro.
O Facebook pede que você liste sua cidade natal e sua localização atual ostensivamente para facilitar a localização e a conexão com você. Mas também analisa esses locais para identificar padrões globais de migração e onde vivem as bases de fãs de diferentes times de futebol.
Como grande varejista, a Target rastreia suas compras e interações, on-line e na loja. E ele usa os dados para modelar preditiva qual de seus clientes está grávida, para melhor comercializar compras relacionadas ao bebê para eles.
Em 2012, a campanha de Obama empregou dezenas de cientistas de dados que se dedicaram a identificar os eleitores que precisavam de atenção extra, escolhendo apelos e programas de captação de recursos específicos para doadores e focar os esforços de obter o voto, onde provavelmente eram úteis. E em 2016, a campanha de Trump testou uma variedade impressionante de anúncios online e analisou os dados para encontrar o que funcionou e o que não funcionou.
Agora, antes de começar a se sentir muito cansado: alguns cientistas de dados também usam ocasionalmente suas habilidades para coisas boas, como utilizar dados para tornar o governo mais eficaz, para ajudar os sem teto e para melhorar a saúde pública. Mas certamente não prejudicará sua carreira se você gosta de descobrir a melhor maneira de fazer com que as pessoas cliquem em anúncios.
Motivador Hipotético: Datasciencester
Parabéns! Você acabou de ser contratado para liderar os esforços de ciência de dados no Datasciencester, a rede social para cientistas de dados.
Apesar de ser para cientistas de dados, o DataSciencester nunca investiu na construção de sua própria prática de ciência de dados (para ser justo, o DataSciencester nunca investiu na construção de seu produto). Esse será o seu trabalho! Ao longo do nosso estudo, aprenderemos sobre conceitos de ciência de dados resolvendo problemas que você encontra no trabalho. Às vezes, analisamos os dados explicitamente fornecidos pelos usuários, às vezes analisamos os dados gerados por meio de suas interações com o site e, às vezes, analisaremos os dados de experimentos que projetaremos.
E como o DataSciencester tem uma forte mentalidade de "não inventada", criaremos nossas próprias ferramentas a partir do zero. No final, você terá um entendimento bastante sólido dos fundamentos da ciência de dados. E você estará pronto para aplicar suas habilidades em uma empresa com uma premissa menos instável ou a quaisquer outros problemas que lhe interessam.
Bem vindo a bordo, e boa sorte! (Você tem permissão para usar bermudas às sextas-feiras, e o banheiro fica no corredor à direita).
Encontrando os principais conectores
É o seu primeiro dia no trabalho no Datasciencester, e o vice-presidente de rede está cheio de perguntas sobre seus usuários. Até agora, ele não tinha ninguém para perguntar, então está muito empolgado por tê-lo a bordo.
Em particular, ele quer que você identifique quem são os "principais conectores" estão entre os cientistas de dados. Para esse fim, ele fornece um despejo de toda a rede Datasciencester. (Na vida real, as pessoas normalmente não entregam os dados necessários. O Capítulo 8 é dedicado a obter dados).
Como é esse despejo de dados? Consiste em uma lista de usuários, cada um representado por um dicionário que contém o ID desse usuário (que é um número) e o nome:
usuarios = [
{'id': 0, 'nome': 'João'},
{'id': 1, 'nome': 'Maria'},
{'id': 2, 'nome': 'Lucas'},
{'id': 3, 'nome': 'José'},
{'id': 4, 'nome': 'Luiza'},
{'id': 5, 'nome': 'Rivaldo'},
{'id': 6, 'nome': 'Sônia'},
{'id': 7, 'nome': 'Claudia'},
{'id': 8, 'nome': 'Thiago'},
{'id': 9, 'nome': 'Arthur'},
]
Ele também fornece os dados de "amizade", representados como uma lista de pares de IDs:
pares_amizades = [(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 exemplo, a tupla (0, 1) indica que o cientista de dados com ID 0 (João) e o cientista de dados com ID 1 (Maria) são amigos. A rede é ilustrada na Figura 1.
Figura 1: A rede Datasciencester.
Ter amizades representadas como uma lista de pares não é a maneira mais fácil de trabalhar com elas. Para encontrar todas as amizades do Usuário 1, você precisa iterar em todos os pares que procuram pares contendo 1. Se você tivesse muitos pares, isso levaria muito tempo.
Em vez disso, vamos criar um dicionário em que as chaves são IDs de usuário e os valores são listas de IDs de amigos (pesquisar as coisas em um dicionário é muito rápido).
Ainda teremos que olhar para todos os pares para criar o dicionário, mas só precisamos fazer isso uma vez, e teremos pesquisas rápidas e de baixo custo computacional depois disso:
# --- Inicializar o dicionário com uma lista vazia para cada ID --- #
amizades = {usuario['id']: [] for usuario in usuarios}
# --- Iterar sobre cada par de conexão entre os usuários --- #
for i, j in pares_amizades:
amizades[i].append(j) # adicionar j como amigo de i
amizades[j].append(i) # adicionar i como amigo de j
Agora que temos as amizades em um dicionário, podemos facilmente fazer perguntas ao nosso gráfico, como: qual é o número médio de conexões?
Primeiro, encontramos o número total de conexões, somando o total de conexões em cada lista de amigos:
def numero_amigos(usuario):
"""Função que determina a quantidade de conexões totais."""
id_usuario = usuario['id']
ids_amigos = amizades[id_usuario]
return len(ids_amigos)
total_conexoes = sum(numero_amigos(usuario) for usuario in usuarios)
E então apenas dividimos pelo número de usuários:
numero_usuarios = len(usuarios)
media_conexoes = total_conexoes / numero_usuarios
Também é fácil encontrar as pessoas mais conectadas, elas são as pessoas que têm o maior número de amigos.
Como não há muitos usuários, podemos simplesmente classificá-los da "mais amigos" a "menos amigos":
# --- Criar uma lista com o número de ID e a quantidade de amigos --- #
quantidade_amigos = [(usuario['id'], numero_amigos(usuario)) for usuario in usuarios]
# --- Ordenar da maior quantidade de amigos para a menor --- #
quantidade_amigos.sort(
key=lambda tupla_usuario: tupla_usuario[1],
reverse=True
)
Uma maneira de pensar no que fizemos é como uma maneira de identificar pessoas que são de alguma forma centrais para a rede. De fato, o que acabamos de calcular é a centralidade da métrica de rede (Figura 2).
Figura 2: A rede Datasciencester dimensionada por conexões.
Isso tem a virtude de ser muito fácil de calcular, mas nem sempre fornece os resultados que você deseja ou espera. Por exemplo, na rede DataSciencester Luiza (ID 4) possui apenas duas conexões, enquanto Maria (ID 1) tem três. No entanto, quando olhamos para a rede, parece intuitivamente que Luiza deve ser mais central. No Capítulo 21, investigaremos redes com mais detalhes e analisaremos noções mais complexas de centralidade que podem ou não concordar melhor com a nossa intuição.
Cientistas de dados que você pode conhecer
Enquanto você ainda está preenchendo a documentação de contratação nova, o vice-presidente de fraternização vem à sua mesa. Ela quer incentivar mais conexões entre seus membros e pede que você projete um sugestionador de “cientistas de dados que você conhece”.
Seu primeiro instinto é sugerir que os usuários possam conhecer os amigos de seus amigos. Então você escreve algum código para iterar sobre seus amigos e coletar os amigos dos amigos:
def id_amigos_amigo(usuario):
"""Função que retorna o ID de amigos de amigo."""
return [id_amigo_de_amigo
for id_amigo in amizades[usuario['id']]
for id_amigo_de_amigo in amizades[id_amigo]]
Quando chamamos usuarios[0] (João), ele produz:
[0, 2, 3, 0, 1, 3]
Inclui o usuário 0 duas vezes, já que João é realmente amigo de seus amigos. Inclui os usuários 1 e 2, embora já sejam amigos do João. E inclui o usuário 3 duas vezes, pois o José é acessível através de dois amigos diferentes:
print(amizades[0]) # [1, 2]
print(amizades[1]) # [0, 2, 3]
print(amizades[2]) # [0, 1, 3]
Saber que as pessoas são amigos de amigos de várias maneiras parecem informações interessantes, então talvez devamos produzir uma contagem de amigos em comum. E provavelmente devemos excluir as pessoas já conhecidas pelo usuário:
from collections import Counter
def amigos_de_amigos(usuario):
"""Função que retorna os amigos de amigos."""
id_usuario = usuario['id']
# --- Para cada amigo meu, encontrar os amigos que não são meus amigos nem o meu próprio ID --- #
return Counter(
id_amigo_de_amigo
for id_amigo in amizades[id_usuario]
for id_amigo_de_amigo in amizades[id_amigo]
if id_amigo_de_amigo != id_usuario
and id_amigo_de_amigo not in amizades[id_usuario]
)
print(amigos_de_amigos(usuarios[3]))
Isso diz corretamente a José (ID 3) que ela tem dois amigos em comum com João (ID 0), mas apenas um amigo em comum com Rivaldo (ID 5).
Como cientista de dados, você sabe que também pode gostar de conhecer usuários com interesses semelhantes. (este é um bom exemplo do aspecto de “experiência substantiva” da ciência de dados). Depois de perguntar, você consegue colocar suas mãos nesses dados, como uma lista de pares (id_usuario, interesse):
interesses = [
(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, 'modelos estatísticos'), (2, 'pandas'),
(3, 'R'), (3, 'Python'), (3, 'estatística'), (3, 'regressão'), (3, 'probabilidade'),
(4, 'machine learning'), (4, 'regressão'), (4, 'árvores de decisão'), (4, 'libsvm'),
(5, 'Python'), (5, 'R'), (5, 'Java'), (5, 'C++'), (5, 'Haskell'), (5, 'linguagens de programação'),
(6, 'estatística'), (6, 'probabilidade'), (6, 'matemática'), (6, 'teoria'),
(7, 'machine learning'), (7, 'scikit-learn'), (7, 'Mahout'), (7, 'redes neurais'),
(8, 'redes neurais'), (8, 'deep learning'), (8, 'Big Data'), (8, 'intligência artificial'),
(9, 'Hadoop'), (9, 'Java'), (9, 'MapReduce'), (9, 'Big Data')
]
Por exemplo, o João (ID 0) não tem amigos em comum com Arthur (ID 9), mas eles compartilham interesses em Java e Big Data. É fácil construir uma função que encontre os usuários com um certo interesse:
def interesses_usuarios(interesse_comum):
"""Função que mostra os IDs dos usuários com interesse em comum."""
return [id_usuario
for id_usuario, interesse_usuario in interesses
if interesse_usuario == interesse_comum]
Isso funciona, mas precisa examinar toda a lista de interesses para todas as pesquisas. Se tivermos muitos usuários e interesses (ou se queremos apenas fazer muitas pesquisas), provavelmente estamos melhor construindo um índice de interesses para usuários:
from collections import defaultdict
# --- As chaves são os interesses e os valores são uma lista com os IDs dos usuários --- #
id_usuario_por_interesse = defaultdict(list)
for id_usuario, interesse in interesses:
id_usuario_por_interesse[interesse].append(id_usuario)
E outro dos usuários para interesses:
# --- As chaves são os IDs dos usuários e os valores são uma lista com os interesses --- #
interesse_por_id_usuario = defaultdict(list)
for id_usuario, interesse in interesses:
interesse_por_id_usuario[id_usuario].append(interesse)
Agora é fácil encontrar quem tem mais interesses em comum com um determinado usuário:
Iterar sobre os interesses do usuário;
Para cada interesse, iterar sobre os outros usuários com esse interesse e;
Mantenha a conta de quantas vezes vimos o outro usuário.
Em código:
def interesses_comum_usuarios(usuario):
"""Função mostra quais são os interesses em comum com outros usuários."""
return Counter(
usuario_id_interesse
for interesse in interesse_por_id_usuario[usuario['id']]
for usuario_id_interesse in id_usuario_por_interesse[interesse]
if usuario_id_interesse != usuario['id']
)
Poderíamos então usar isso para criar um recurso mais rico de “cientistas de dados que você conhece” com base em uma combinação de amigos mútuos e interesses mútuos. Exploraremos esses tipos de aplicações no Capítulo 22.
Salários e experiência
Certo, quando você está prestes a almoçar, o vice-presidente de relações públicas pergunta se você pode fornecer alguns fatos divertidos sobre a quantidade de dados que os cientistas ganham. É claro que os dados salariais são sensíveis, mas ele consegue fornecer um conjunto de dados anônimos contendo o salário de cada usuário (em dólares) e o tempo de contratado como cientista de dados (em anos):
salarios_tempo_contratado = [
(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)
]
O primeiro passo natural é plotar os dados (que veremos como fazer no Capítulo 2). Você pode ver os resultados na Figura 3:
Figura 3: Gráfico do salário por tempo contratado.
Parece claro que as pessoas com mais experiência tendem a ganhar mais. Como você pode transformar isso em um fato divertido? Sua primeira ideia é olhar para o salário médio para cada tempo contratado:
# --- As chaves são os anos e os valores são uma lista com o salário --- #
salario_por_tempo_contratado = defaultdict(list)
for salario, tempo_contratato in salarios_tempo_contratado:
salario_por_tempo_contratado[tempo_contratato].append(salario)
# --- As chaves são os anos e os valores são a média salarial --- #
media_salarial_por_tempo = {
tempo_contratato: sum(salarios) / len(salarios)
for tempo_contratato, salarios in salario_por_tempo_contratado.items()
}
print(media_salarial_por_tempo)
Isso não é particularmente útil, pois nenhum dos usuários tem o mesmo tempo contratado, o que significa que estamos apenas relatando os salários dos usuários individuais. Pode ser mais útil separar em intervalos o tempo de contrato:
def intervalo_contrato(tempo_contrato):
"""Função que classifica o tempo de contrato."""
if tempo_contrato < 2:
return 'Menor que 2 anos'
elif tempo_contrato < 5:
return 'Entre 2 e 5 anos'
else:
return 'Mais do que 5 anos'
Então podemos agrupar os salários correspondentes a cada intervalo:
# --- As chaves são os intervalos e os valores são as médias do salário do intervalo --- #
salario_por_intervalo = defaultdict(list)
for salario, tempo_contratato in salarios_tempo_contratado:
intervalo = intervalo_contrato(tempo_contratato)
salario_por_intervalo[intervalo].append(salario)
E finalmente calcule o salário médio para cada intervalo:
# --- As chaves são os intervalos e os valores são a média salarial do intervalo --- #
media_salarial_por_intervalo = {
intervalo: sum(salarios) / len(salarios)
for intervalo, salarios in salario_por_intervalo.items()
}
O que é mais interessante:
Menor que 2 anos: US$ 48.000.00
Entre 2 e 5 anos: US$ 61.500,00
Mais do que 5 anos: US$ 79.166,66
E você tem sua resposta: cientistas de dados com mais de cinco anos de experiência ganham 65% a mais do que os cientistas de dados com pouca ou nenhuma experiência!
Mas escolhemos os intervalos de uma maneira bastante arbitrária. O que realmente gostaríamos é fazer alguma declaração sobre o efeito salarial - em média - de ter um ano adicional de experiência. Além de criar um fato divertido mais rápido, isso nos permite fazer previsões sobre salários que não conhecemos. Vamos explorar essa ideia no Capítulo 13.
Contas pagas
Quando você volta para sua mesa, o vice-presidente de receita está esperando por você. Ela quer entender melhor quais usuários pagam por contas e quais não. (ele conhece os nomes deles, mas isso não é uma informação particularmente acionável).
Você percebe que parece haver uma correspondência entre anos de experiência e contas pagas:
0.7 paga
1.9 não paga
2.5 paga
4.2 não paga
6.0 não paga
6.5 não paga
7.5 não paga
8.1 não paga
8.7 paga
10.0 paga
Usuários com poucos e muitos anos de experiência tendem a pagar; Usuários com quantidades médias de experiência não. Consequentemente, se você quiser criar um modelo - embora isso definitivamente não seja dados suficientes para basear um modelo - você pode tentar prever "pago" para usuários com poucos e muitos anos de experiência e "não paga" para usuários com quantidades mediantes de experiência:
def prever_pago_nao_pago(anos_experiencia):
"""Função que prevê se o usuário paga algo na rede social baseado em seu tempo de experiência."""
if anos_experiencia < 3:
return 'paga'
elif anos_experiencia < 8.5:
return 'não paga'
else:
'paga'
Nós obtivemos totalmente os pontos de corte.
Com mais dados (e mais matemática), poderíamos construir um modelo prevendo a probabilidade de um usuário pagar com base em seus anos de experiência. Investigaremos esse tipo de problema no Capítulo 15.
Tópicos de interesse
Ao encerrar seu primeiro dia, o vice-presidente de estratégia de conteúdo solicita dados sobre o que os usuários de tópicos estão mais interessados, para que ela possa planejar seu calendário de blog de acordo. Você já tem os dados brutos do sugestor de amigos:
interesses = [
(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, 'modelos estatísticos'), (2, 'pandas'),
(3, 'R'), (3, 'Python'), (3, 'estatística'), (3, 'regressão'), (3, 'probabilidade'),
(4, 'machine learning'), (4, 'regressão'), (4, 'árvores de decisão'), (4, 'libsvm'),
(5, 'Python'), (5, 'R'), (5, 'Java'), (5, 'C++'), (5, 'Haskell'), (5, 'linguagens de programação'),
(6, 'estatística'), (6, 'probabilidade'), (6, 'matemática'), (6, 'teoria'),
(7, 'machine learning'), (7, 'scikit-learn'), (7, 'Mahout'), (7, 'redes neurais'),
(8, 'redes neurais'), (8, 'deep learning'), (8, 'Big Data'), (8, 'intligência artificial'),
(9, 'Hadoop'), (9, 'Java'), (9, 'MapReduce'), (9, 'Big Data')
]
Uma maneira simples (se não particularmente emocionante) de encontrar os interesses mais populares é contar as palavras:
Deixar em minúsculo cada interesse (uma vez que diferentes usuários podem ou não capitalizar seus interesses);
Dividi-lo em palavras e;
Contar o resultado.
Em código:
contagem_palavras = Counter(palavra
for usuario, interesse in interesses
for palavra in interesse.lower().split())
Isso facilita a lista das palavras que ocorrem mais de uma vez:
for palavra, contagem in contagem_palavras.most_common():
if contagem > 1:
print(palavra, contagem)
O que fornece os resultados que você espera. A menos que você espere "Scikit-Learn" se dividir em duas palavras; nesse caso, não fornece os resultados que você espera.
Veremos maneiras mais sofisticadas de extrair tópicos dos dados no Capítulo 20.
Adiante
Foi um primeiro dia de sucesso! Exausto, você desce do prédio antes que alguém possa pedir qualquer outra coisa. Tenha uma boa noite de descanso, porque amanhã é uma nova orientação para funcionários (sim, você passou por um dia inteiro de trabalho antes da orientação de novos funcionários. Converse com o RH depois).