Tópico 9 – Funções e Apply
Vamos aprender sobre funções Python e como aplicar as mesmas em DataFrame
.
Resultados Esperados
- Entender como definir funções
def
- Entender como aplicar funções em
DataFrame
s (apply
)
Material Adaptado do DSC10 (UCSD)
#In:
import babypandas as bpd
import matplotlib.pyplot as plt
import numpy as np
plt.style.use('ggplot')
Agenda
- Funções.
- Aplicando funções a DataFrames.
- Exemplo: Nomes de alunos.
Funções
Definindo funções
- Aprendemos bastante como fazer em Python:
- Manipular arrays, séries e DataFrames.
- Execute operações em strings.
- Crie visualizações.
- Mas até agora, estamos restritos ao uso de funções existentes (por exemplo,
max
,np.sqrt
,len
) e métodos (por exemplo,.groupby
,.assign
,.plot
).
Motivação
Suponha que você dirija até um restaurante 🥘 em Ouro Preto, localizado a exatamente 100 quilômetros de distância.
- Nos primeiros 80 quilômetros, você dirige a 80 quilômetros por hora.
Nos últimos 20 quilômetros, você dirige a 60 quilômetros por hora.
Pergunta: Qual é a sua velocidade média durante a viagem?
- 🚨 A resposta não é 70 quilômetros por hora! Você precisa usar o fato de que $\text{velocidade} = \frac{\text{distancia}}{\text{tempo}}$.
No segmento 1, quando você dirigiu 80 quilômetros a 80 quilômetros por hora, você dirigiu por $\frac{80}{80}$ horas:
\[\text{velocidade}_1 = \frac{\text{distância}_1}{\text{tempo}_1}\] \[80 \text{ km por hora} = \frac{80 \text{ km}}{\text{time}_1} \implies \text{time}_1 = \frac{80}{80} \text{ horas} = 1\]Da mesma forma, no segmento 2, quando você dirigiu 20 quilômetros a 60 quilômetros por hora, você dirigiu por $\text{time}_2 = \frac{20}{60} \text{ horas} = \frac{1}{3} horas$.
Então,
\[\text{velocidade média} = \frac{80 + 20}{\frac{1}{1} + \frac{1}{3}} \text{ km por hora}\] \[\begin{align*}\text{velocidade média} &= 100 \cdot \frac{1}{\frac{1}{1} + \frac{1}{3}} \text{ km por hora} \\ &= 100 \frac{1}{\frac{3 + 1}{3}} \\ &= 100 \frac{3}{4} \\ &= 75 \text{ km por hora}\end{align*}\]Exemplo: média harmônica
A média harmônica ($\text{HM}$) de dois números positivos, $a$ e $b$, é definida como
\[\text{HM} = \frac{2}{\frac{1}{a} + \frac{1}{b}}\]Geralmente é usado para encontrar a média de múltiplas taxas.
Encontrar a média harmônica de 80 e 60 não é difícil:
#In:
2 / (1 / 1 + 1 / 3)
1.5
Mas e se quisermos determinar a média harmónica de 80 e 70? 80 e 90? 20 e 40? Isso exigiria muito copiar e colar, o que é propenso a erros.
Acontece que podemos definir nossa própria função de “média harmônica” **apenas uma vez e reutilizá-la várias vezes.
#In:
def harmonic_mean(a, b):
return 2 / (1 / a + 1 / b)
#In:
harmonic_mean(1, 3)
1.5
#In:
harmonic_mean(1, 5)
1.6666666666666667
Observe que só tivemos que especificar como calcular a média harmônica uma vez!
Funções
Funções são uma forma de dividir nosso código em pequenas subpartes para evitar que escrevamos código repetitivo. Cada vez que definirmos nossa própria função em Python, usaremos o seguinte padrão.
#In:
from IPython.display import display, IFrame
def show_def():
src = "https://docs.google.com/presentation/d/e/2PACX-1vRKMMwGtrQOeLefj31fCtmbNOaJuKY32eBz1VwHi_5ui0AGYV3MoCjPUtQ_4SB1f9x4Iu6gbH0vFvmB/embed?start=false&loop=false&delayms=60000"
width = 960
height = 569
display(IFrame(src, width, height))
show_def()
Funções são “receitas”
- As funções recebem entradas, conhecidas como argumentos, fazem algo e produzem algumas saídas.
- A beleza das funções é que você não precisa saber como elas são implementadas para usá-las!
- Esta é a premissa da ideia de abstração na ciência da computação – você ouvirá muito sobre isso no DSC 20.
#In:
harmonic_mean(1, 1)
1.0
#In:
harmonic_mean(1, 3)
1.5
#In:
harmonic_mean(1, 2)
1.3333333333333333
Parâmetros e argumentos
triple
tem um parâmetro, x
.
#In:
def triple(x):
return x * 3
Quando chamamos triple
com o argumento 5, você pode fingir que há uma primeira linha invisível no corpo de triple
que diz x = 5
.
#In:
triple(5)
15
Observe que os argumentos podem ser de qualquer tipo!
#In:
triple('triton')
'tritontritontriton'
Funções podem receber 0 ou mais argumentos
As funções podem ter qualquer número de argumentos. Até agora, criamos uma função que leva dois argumentos – harmonic_mean
– e uma função que leva um argumento – triple
.
saudação
não aceita argumentos!
#In:
def greeting():
return 'Hi! 👋'
#In:
greeting()
'Hi! 👋'
As funções não são executadas até que você as chame!
O corpo de uma função não é executado até que você use (call) a função.
Aqui, podemos definir where_is_the_error
sem ver uma mensagem de erro.
#In:
def where_is_the_error(something):
'''You can describe your function within triple quotes. For example, this function
illustrates that errors don't occur until functions are executed (called).'''
return (1 / 0) + something
Somente quando chamamos where_is_the_error
que o Python nos dá uma mensagem de erro.
#In:
where_is_the_error(5)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In[16], line 1
----> 1 where_is_the_error(5)
Cell In[15], line 4, in where_is_the_error(something)
1 def where_is_the_error(something):
2 '''You can describe your function within triple quotes. For example, this function
3 illustrates that errors don't occur until functions are executed (called).'''
----> 4 return (1 / 0) + something
ZeroDivisionError: division by zero
Exemplo: primeiro_nome
Vamos criar uma função chamada first_name
que recebe o nome completo de alguém e retorna seu primeiro nome. Um exemplo de comportamento é mostrado abaixo.
>>> first_name('Flavio Figueiredo')
'Flavio'
Dica: Use o método string .split
.
Estratégia geral para escrever funções:
- Primeiro, tente fazer com que o comportamento funcione em um único exemplo.
- Em seguida, encapsule esse comportamento dentro de uma função.
#In:
'Flavio Figueiredo'.split(' ')[0]
'Flavio'
#In:
def first_name(full_name):
'''Returns the first name given a full name.'''
return full_name.split(' ')[0]
#In:
first_name('Flavio Figueiredo')
'Flavio'
#In:
# What if there are three names?
first_name('Mestre Flavio Figueiredo')
'Mestre'
Retornando
- A palavra-chave
return
especifica qual deve ser a saída da sua função, ou seja, como será avaliada uma chamada para a sua função. - A maioria das funções que escrevemos usará
return
, mas usarreturn
não é obrigatório. - Tenha cuidado:
print
ereturn
funcionam de forma diferente!
#In:
def pythagorean(a, b):
'''Computes the hypotenuse length of a triangle with legs a and b.'''
c = (a ** 2 + b ** 2) ** 0.5
print(c)
#In:
x = pythagorean(3, 4)
5.0
#In:
# No output – why?
x
#In:
# Errors – why?
x + 10
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[24], line 2
1 # Errors – why?
----> 2 x + 10
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
#In:
def better_pythagorean(a, b):
'''Computes the hypotenuse length of a triangle with legs a and b, and actually returns the result.'''
c = (a ** 2 + b ** 2) ** 0.5
return c
#In:
x = better_pythagorean(3, 4)
x
5.0
#In:
x + 10
15.0
Retornando
Depois que uma função executa uma instrução return
, ela para de funcionar.
#In:
def motivational(quote):
return 0
print("Uma frase motivacional:", quote)
#In:
motivational('Caia sete vezes e se levante oito.')
0
Escopo 🩺
Os nomes que você escolhe para os parâmetros de uma função são conhecidos apenas por essa função (conhecido como escopo local). O restante do seu notebook não é afetado pelos nomes dos parâmetros.
#In:
def what_is_awesome(s):
return s + ' is awesome!'
#In:
what_is_awesome('data science')
'data science is awesome!'
#In:
# descomente para ver o erro
# s
#In:
s = 'FCD'
#In:
what_is_awesome('data science')
'data science is awesome!'
Aplicando funções a DataFrames
Dados dos alunos de FCD
A df
do DataFrame contém os nomes de todos os alunos matrículados em FCD.
#In:
nomes = 'ANNY \
ARTHUR \
ARTHUR \
CAIO \
CAROLINA \
CLARA \
DANIELLE \
EDUARDO \
EDUARDO \
EMANUEL \
ENZO \
FELIPE \
FELIPE \
FRANCISCO \
GABRIEL \
GABRIEL \
GABRIELLY \
GAEL \
GUILHERME \
GUILHERME \
GUSTAVO \
ISAAC \
JOAO \
JOAO \
KARINA \
LETICIA \
LETICIA \
LIVIA \
LORRANY \
LUCAS \
LUIS \
MARCO \
MATEUS \
MATEUS \
MATHEUS \
RAIZA \
RENATO \
SOPHIA \
THAYRELAN \
VICTOR'
#In:
df = bpd.DataFrame().assign(
nome=nomes.split()
)
df = df.sample(df.shape[0])
df
nome | |
---|---|
18 | GUILHERME |
23 | JOAO |
37 | SOPHIA |
7 | EDUARDO |
2 | ARTHUR |
... | ... |
13 | FRANCISCO |
4 | CAROLINA |
28 | LORRANY |
27 | LIVIA |
30 | LUIS |
40 rows × 1 columns
Exemplo: qual a primeira letra mais comum entre os nomes dos discentes de FCD?
Problema: Não podemos responder agora, pois não temos uma coluna com primeira letra. Se o fizéssemos, poderíamos agrupar por ele.
Solução: Criar uma função.
Criando uma função primeira_letra
De alguma forma, precisamos chamar 'primeira_letra'
no 'nome'
de cada aluno.
#In:
def primeira_letra(nome):
return nome[0]
#In:
primeira_letra('FLAVIO')
'F'
#In:
primeira_letra(df.get('nome').iloc[0])
'G'
#In:
primeira_letra(df.get('nome').iloc[1])
'J'
Idealmente, existe uma solução melhor do que fazer isso centenas de vezes…
.apply
- Para aplicar uma função a cada elemento da coluna
column_name
no DataFramedf
, use
df.get(column_name).apply(function_name)
- O método
.apply
é um método de uma Series não de um DataFrame. - Importante: Usamos
.apply
em séries, não em DataFrames. A saída de
.apply
também é uma série.- Passe apenas o nome da função – não a chame!
- Bom ✅:
.apply(primeira_letra)
. - Ruim ❌:
.apply(primeira_letra())
.
#In:
df.get('nome').apply(primeira_letra)
18 G
23 J
37 S
7 E
2 A
..
13 F
4 C
28 L
27 L
30 L
Name: nome, Length: 40, dtype: object
Exemplo: nomes próprios comuns
#In:
df = df.assign(
primeira=df.get('nome').apply(primeira_letra)
)
df
nome | primeira | |
---|---|---|
18 | GUILHERME | G |
23 | JOAO | J |
37 | SOPHIA | S |
7 | EDUARDO | E |
2 | ARTHUR | A |
... | ... | ... |
13 | FRANCISCO | F |
4 | CAROLINA | C |
28 | LORRANY | L |
27 | LIVIA | L |
30 | LUIS | L |
40 rows × 2 columns
#In:
letra_count = (df.
groupby('primeira').
size().
sort_values(ascending=False)
)
letra_count
primeira
G 7
L 6
E 4
M 4
A 3
..
I 1
K 1
S 1
T 1
V 1
Length: 15, dtype: int64
Atividade
Abaixo:
- Crie um gráfico de barras para a
primeira
eultima
letra de cada nome. - O que você consegue tirar dos dois gráficos?
#In:
...
Ellipsis
#In:
...
Ellipsis
Nota: .apply
também funciona com funções já existentes!
Por exemplo, para encontrar o comprimento de cada nome, podemos usar a função len
:
#In:
df
nome | primeira | |
---|---|---|
18 | GUILHERME | G |
23 | JOAO | J |
37 | SOPHIA | S |
7 | EDUARDO | E |
2 | ARTHUR | A |
... | ... | ... |
13 | FRANCISCO | F |
4 | CAROLINA | C |
28 | LORRANY | L |
27 | LIVIA | L |
30 | LUIS | L |
40 rows × 2 columns
#In:
df.get('nome').apply(len)
18 9
23 4
37 6
7 7
2 6
..
13 9
4 8
28 7
27 5
30 4
Name: nome, Length: 40, dtype: int64
Atividade
Encontre o nome mais curto da turma que seja compartilhado por pelo menos dois alunos na mesma seção.
Dica: Você terá que usar .assign
e .apply
.
#In:
...
Ellipsis
Resumo, da próxima vez
Resumo
- Funções são uma forma de dividir nosso código em pequenas subpartes para evitar que escrevamos código repetitivo.
- O método
.apply
nos permite chamar uma função em cada elemento de uma Série, o que geralmente vem de.get
ting uma coluna de um DataFrame.
Próxima vez
Manipulações mais avançadas de DataFrame!