Dataframes: multi-índices e concatenção

Índices Hierárquicos

É possível criar series e dataframes com índices e subíndices. Esse processo de indexação hierárquica é importante para a reformatação (reshaping ), formação de tabelas pivot e outras operações de agrupamento de dados.

» import pandas as pd
» import numpy as np

» # formamos uma series com índices duplos 
» sr = pd.Series([11, 12, 21, 22, 23, 31, 32, 41, 42],
                 index=[['A', 'A', 'B', 'B', 'B', 'C', 'C', 'D', 'D'],
                 [1, 2, 1, 2, 3, 1, 2, 1, 2]])
» sr
↳ A  1    11
     2    12
  B  1    21
     2    22
     3    23
  C  1    31
     2    32
  D  1    41
     2    42

» # essa series possui índices
» sr.index
↳ MultiIndex([('A', 1), ('A', 2),
              ('B', 1), ('B', 2), ('B', 3),
              ('C', 1), ('C', 2),
              ('D', 1), ('D', 2)],)

» # da mesma forma podemos transformar essa series um um dataframe
» df = pd.DataFrame(sr)

» # Os índices do dataframe são os mesmos: df.index

» # o índice B corresponde à 3 linhas
» df.loc['B']
↳        0
  1     21
  2     22
  3     23

» df.loc['B'].loc[2]
↳ 0    22

» # idem para a series
» sr['C']
↳ 1    31
  2    32

» sr['C'][1]
↳ 31

» # podemos listar as linhas de 'A' até 'C'
» sr['A':'C']
↳ A  1    11
     2    12
  B  1    21
     2    22
     3    23
  C  1    31
     2    32

» # ou as linhas correspondentes à 'A' e 'C'
» sr.loc[['A','C']]
↳ A  1    11
     2    12
  C  1    31
     2    32

» # seleção pelo índice interno pode feita diretamente
» sr.loc[:, 2]
↳ A    12
  B    22
  C    32
  D    42

» sr.loc[:, 3]
↳ B    23

stack() e unstack()

Os dados de uma series com índices hierárquicos podem ser rearranjados em um DataFrame com o uso de método Series.unstack(). Os índices internos se tornam nomes das colunas. Valores não existentes, como o correspondende aos índices A, 3, são preenchidos com NaN.

» df = sr.unstack()
» df
↳          1         2       3
  A     11.0     12.0      NaN
  B     21.0     22.0     23.0
  C     31.0     32.0      NaN
  D     41.0     42.0      NaN

» # para retornar à uma series
» df.unstack()
↳ 1  A    11.0
     B    21.0
     C    31.0
     D    41.0
  2  A    12.0
     B    22.0
     C    32.0
     D    42.0
  3  A     NaN
     B    23.0
     C     NaN
     D     NaN

No processo de desempilhar o dataframe (unstack ) os nomes das colunas foram usados como índices primários.

Um dataframe pode ter índices hierarquizados para linhas e colunas.

» clima = np.array([[25,20,30],[20,16,15],[15,25,27],[40,60,78]])
» dfClima = pd.DataFrame(clima,
»                       index=[['Temperatura','Temperatura','Umidade','Umidade'],
»                              ['dia','noite','dia','noite']],
»                       columns=[['Paraná','Paraná','Amazonas'],['Cascavel','Curitiba','Manaus']]
»                       )

» # inserindo nomes para as linhas e colunas
» dfClima.index.names = ['Característica', 'D/N']        # D/N = dia/noite
» dfClima.columns.names = ['Estado', 'Cidade']

» # o resultado é
» dfClima
↳                  Estado                   Paraná     Amazonas
                   Cidade     Cascavel     Curitiba      Manaus
  Característica      D/N             
  Temperatura         dia           25           20          30
                    noite           20           16          15
  Umidade             dia           15           25          27
                    noite           40           60          78

Se o processo de criação de dataframes com os mesmos índices será repetido várias vezes ,pode ser útil definir previamente os objetos multindexes.

» colunas = pd.MultiIndex.from_arrays([['Paraná', 'Paraná', 'Amazonas'],
»                                      ['Cascavel', 'Curitiba', 'Manaus']],
»                                      names=['Estado', 'Cidade'])

» linhas = pd.MultiIndex.from_arrays([['Temperatura','Temperatura','Umidade','Umidade'],
»                                     ['dia','noite','dia','noite']],
»                                     names=['Característica', 'D/N'])

» linhas
↳ MultiIndex([('Temperatura',   'dia'),
              ('Temperatura', 'noite'),
              (    'Umidade',   'dia'),
              (    'Umidade', 'noite')],
             names=['Característica', 'D/N'])

» pd.DataFrame(clima, index=linhas, columns=colunas)
↳                 Estado     Paraná             Amazonas
                  Cidade   Cascavel    Curitiba   Manaus
  Característica     D/N
  Temperatura        dia         25          20       30
                   noite         20          16       15
  Umidade            dia         15          25       27
                   noite         40          60       78

swaplevel() e groupby()

O ordenamento dos níveis nos dataframes pode ser alterado com o método dataframe.swaplevel(indice1, indice2). Índices primários podem ser permutados com índice secundários. Com dataframe.sort_index(level=n) podemos ordenar as linhas do dataframe segundo os nomes dos índices do nível n.

» dfClima.swaplevel('D/N', 'Característica')
↳                 Estado     Paraná               Amazonas
                  Cidade   Cascavel    Curitiba     Manaus
  D/N     Característica
  dia        Temperatura         25          20         30
  noite      Temperatura         20          16         15
  dia            Umidade         15          25         27
  noite          Umidade         40          60         78

» # ordenando as linhas pelos labels do nível 1 (D/N)
» dfClima.sort_index(level=1)

↳                  Estado                    Paraná    Amazonas
                   Cidade     Cascavel     Curitiba      Manaus
  Característica      D/N
  Temperatura         dia           25           20          30
  Umidade             dia           15           25          27
  Temperatura       noite           20           16          15
  Umidade           noite           40           60          78

» # alternativamente podemos inverter a ordem dos níveis e ordenar pelo nivel 0
» dfClima.swaplevel(0, 1).sort_index(level=0)
↳                 Estado                    Paraná    Amazonas
                  Cidade     Cascavel     Curitiba      Manaus
  D/N     Característica
  dia        Temperatura           25           20          30
                 Umidade           15           25          27
  noite      Temperatura           20           16          15
                 Umidade           40           60          78

» # soma dos valores agrupados pelo nível 1 (D/N)
» dfClima.groupby(level=1).sum()
 
↳ Estado                    Paraná    Amazonas
  Cidade     Cascavel     Curitiba      Manaus
  D/N 
  dia              40           45          57
  noite            60           76          93

O método dataframe.groupby(), que veremos mais tarde com maiores detalhes, permite o agrupamento dos dados de um determinado índice (ou nível de índices). Por ex., dataframe.groupby(level=n).sum() faz o agrupamento dos dados segundo o n-ésimo nível de índice e depois soma esses valores. Muitas outras funções estatísticas ficam disponíveis com agrupamentos por groupby.

» # soma dos valores agrupados pelo nível 0
» dfClima.groupby(level='Característica').mean()
↳                  Estado               Paraná    Amazonas
                   Cidade  Cascavel   Curitiba      Manaus
  Característica
  Temperatura                  22.5       18.0        22.5
  Umidade                      27.5       42.5        52.5

» # a média dos valores agrupados pelo índice D/N
» dfClima.groupby(level=0).mean()

↳                   Estado              Paraná   Amazonas
                    Cidade  Cascavel  Curitiba     Manaus
  Característica
  Temperatura                   22.5      18.0       22.5
  Umidade                       27.5      42.5       52.5

» # o valor máximo agrupado pelo nível 'Característica'
» dfClima.groupby(level='Característica').max()
↳                  Estado              Paraná    Amazonas
  Cidade                   Cascavel   Curitiba     Manaus
  Característica
  Temperatura                    25         20         30
  Umidade                        40         60         78

Vimos previamente que qualquer coluna pode ser transformada em índice do dataframe. Mais de uma coluna pode também ser usada: para isso usamos dataframe.set_index([coluna1, coluna2]). Por default essa operação coloca coluna1, coluna2 como índices e descarta as colunas usadas. Para alterar esse comportamento (e manter as colunas) usamos o parâmetro drop=False. O método dataframe.reset_index() remove os índices colocando-os como colunas e criando um novo conjunto de índices.

» # criamos um dataframe arbitrário
» dfNums = pd.DataFrame({'a': range(1,6),
»                        'texto-a': ['um','dois','três','quatro','cinco'],
»                        'b': range(5, 0, -1),
»                        'texto-b': ['cinco', 'quatro','três','dois','um']
»                       })

» # dataframe inicial
» dfNums
↳      a     texto-a     b     texto-b
  0    1          um     5       cinco
  1    2        dois     4      quatro
  2    3        três     3        três
  3    4      quatro     2        dois
  4    5       cinco     1          um

» # usamos as colunas 'a' e 'b' como índices
» dfNums2 = dfNums.set_index(['a', 'b'])

» dfNums2
↳          texto-a    texto-b
  a    b         
  1    5        um      cinco
  2    4      dois     quatro
  3    3      três       três
  4    2    quatro       dois
  5    1     cinco         um

» # para descartar os índices (e recuperar as colunas)
» dfNums2.reset_index()

↳       a     b     texto-a     texto-b
  0     1     5     um          cinco
  1     2     4     dois        quatro
  2     3     3     três        três
  3     4     2     quatro      dois
  4     5     1     cinco       um

» # podemos usar as colunas 'a' e 'texto-a' como índices sem descartar essas colunas
» dfNums.set_index(['a', 'texto-a'], drop=False)
↳                 a     texto-a    b    texto-b
  a     texto-a                 
  1     um        1     um         5      cinco
  2     dois      2     dois       4     quatro
  3     três      3     três       3       três
  4     quatro    4     quatro     2       dois
  5     cinco     5     cinco      1         um

Uma exceção é lançada se já existem colunas com os mesmos nomes recuperados por reset_index.

Combinando dataframes

Podemos juntar dataframes de várias formas. pandas.merge() junta dataframes usando um ou mais índices, em operações semelhantes àquelas de bancos de dados relacionais usando-se as operações de join do SQL. pandas.concat() faz a concatenação ou empilhamento dos dataframes ao longo do eixo escolhido. pandas.combine_first() permite a junção de dados que se superpõe (existem em mais de uma tabela), preenchendo valores ausentes um uma tabela com aqueles em outra tabela fornecida.

merge()

df1.merge(df2) retorna outro dataframe que é a junção dos dois dataframes. O método possui a seguinte assinatura:
df1.merge(df2, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True, indicator=False, validate=None)

A junção pode ser feita sobre nomes das colunas ou índices. Uma Series nomeada é tratada como um dataframe de coluna única. São parâmetros:

df1, df2 dataframe ou Series nomeada. Junção de df1 com df2
how tipo de junção: left, right, outer, inner, cross:
inner: usa apenas combinações de chaves existentes em ambas as tabelas preserva ordem das chaves.
outer: usa todas as combinações de chaves em cada uma das tabelas,
left: usa todas as combinações de chaves existentes na tabela à esquerda,
right: usa todas as combinações de chaves existentes na tabela à direita,
cross: cria o produto cartesiano das tabelas, preserva ordem dos índices.
on coluna ou índice para a junção. Deve existir em ambos os dataframes
left_on nome da coluna ou índice (ou lista) em df1.
right_on nome da coluna ou índice (ou lista) em df2.
left_index False/True: use o índice de df1 como chave.
right_index False/True: use o índice de df2 como chave.
sort False/True: Ordena os índices no resultado.
suffixes lista: default = (“_x”, “_y”). Sufixos para índices de mesmo nome
copy False/True: Se False evita a cópia, se possível
indicator False/True ou str: Se True acrescenta coluna “_merge” com informações sobre as linhas.
validate str, opcional. Se especificada verifica se a junção é do tipo:
one_to_one ou 1:1 : se chave da fusão é única nos dois dataframes,
one_to_many ou 1:m : se chave da fusão é única em df1 (lado esquerdo),
many_to_one ou m:1 : se chave da fusão é única em df2 (lado direito),
many_to_many ou m:m : embora permitida não resulta em nenhuma verificação.

Comparação de how='' com comandos SQL: (Pandas e SQL comparados).

how= similar ao SQL
left left outer join. Preserva ordem das chaves.
right right outer join. Preserva ordem das chaves.
outer full outer join. Ordena por nomes das chaves.
inner inner join. Preserva ordem das chaves à esquerda.
» # criando dataframes 
» df1 = pd.DataFrame({'chave': ['a', 'a', 'a', 'b', 'b', 'b', 'c'], 'data1': range(7)})
» df2 = pd.DataFrame({'chave': ['a', 'b', 'd'], 'data2': range(3)})

» # exibindo df1, df2 e sua junção com merge
» display(df1, df2, pd.merge(df1, df2))

↳    chave   data1
  0      a       0
  1      a       1
  2      a       2
  3      b       3
  4      b       4
  5      b       5
  6      c       6

↳    chave   data2
  0      a      0
  1      b      1
  2      d      2

↳    chave   data1   data2
  0      a       0       0
  1      a       1       0
  2      a       2       0
  3      b       3       1
  4      b       4       1
  5      b       5       1


Como os dois dataframes possuem uma coluna com nome comum a junção foi feita com base nos valores da coluna com esse nome. Essa informação pode ser explicitada com pd.merge(df1, df2, on='chave').

Se os nomes das colunas de cada dataframe for diferente eles devem ser definidos com os parâmetros left_on, right_on.

» df3 = pd.DataFrame({'chave1': ['a', 'a', 'a', 'b', 'b', 'c', 'd'], 'data1': range(7)})
» df4 = pd.DataFrame({'chave2': ['a', 'b', 'd'], 'data2': range(3)})

» display(df3, df4, pd.merge(df3, df4, left_on='chave1', right_on='chave2'))
↳    chave1     data1
  0       a     0
  1       a     1
  2       a     2
  3       b     3
  4       b     4
  5       c     5
  6       d     6

↳    chave2     data2
  0       a     0
  1       b     1
  2       d     2

↳    chave1     data1     chave2     data2
  0       a     0         a          0
  1       a     1         a          0
  2       a     2         a          0
  3       b     3         b          1
  4       b     4         b          1
  5       d     6         d          2

Vemos na concatenação acima que o método usado reune apenas valores existentes nas duas tabelas. Isso é equivalente a passar o parâmetro how=’inner’ (um inner join ). Outra opção consiste em fazer o ligamento externo.

» # para conseguir um outer join    
» pd.merge(df1, df2, how='outer')

↳     chave   data1   data2
  0     a       0.0     0.0
  1     a       1.0     0.0
  2     a       2.0     0.0
  3     b       3.0     1.0
  4     b       4.0     1.0
  5     b       5.0     1.0
  6     c       6.0     NaN
  7     d       NaN     2.0

» dd.merge(df1, df2, how='left')

↳   chave   data1   data2
  0     a       0     0.0
  1     a       1     0.0
  2     a       2     0.0
  3     b       3     1.0
  4     b       4     1.0
  5     b       5     1.0
  6     c       6     NaN

» pd.merge(df1, df2, how='right')

↳   chave     data1   data2
  0     a       0.0       0
  1     a       1.0       0
  2     a       2.0       0
  3     b       3.0       1
  4     b       4.0       1
  5     b       5.0       1
  6     d       NaN       2

Tabelas podem ser ligadas por mais de uma chave, quando os dataframes possuem índices hierarquizados. As chaves são usadas como se fossem uma única chave concatenada.

» df1 = pd.DataFrame({'chave_1': ['rato', 'rato', 'gato'],
»                      'chave_2': ['Jones', 'Jerry', 'Tom'],
»                      'valor_A': [10, 20, 30]})
» df2 = pd.DataFrame({'chave_1': ['rato', 'rato', 'gato', 'gato'],
»                       'chave_2': ['Jones', 'Jerry', 'Tom', 'Tim'],
»                       'valor_B': [40, 50, 60, 70]})
                      
» # exibindo os dataframes e a junção externa em duas chaves
» display(df1, df2, pd.merge(df1, df2, on=['chave_1','chave_2'], how='outer'))

↳     chave_1     chave_2    valor_A
  0      rato       Jones         10
  1      rato       Jerry         20
  2      gato         Tom         30

↳    chave_1     chave_2   valor_B
  0     rato       Jones        40
  1     rato       Jerry        50
  2     gato         Tom        60
  3     gato         Tim        70

↳     chave_1    chave_2    valor_A    valor_B
  0     rato       Jones       10.0         40
  1     rato       Jerry       20.0         50
  2     gato        Tom        30.0         60
  3     gato        Tim         NaN         70

» # a junção interna em duas chaves
» pd.merge(df1, df2, on=['chave_1','chave_2'], how='inner')

↳     chave_1   chave_2    valor_A    valor_B
  0      rato     Jones         10         40
  1      rato     Jerry         20         50
  2      gato      Tom          30         60

Se a junção for feita sobre campos (nomes de colunas) com o mesmo nome estes serão alterados para continuar a representar suas colunas de origem. No caso do exemplo as colunas com nome valor foram renomeadas para valor_x e valor_y.

» df1 = pd.DataFrame({'chave': ['a', 'b', 'c'], 'valor': [1,2,3]})
» df2 = pd.DataFrame({'chave': ['a', 'b', 'c'], 'valor': [10,20,30]})

» mrg = pd.merge(df1, df2, on='chave')

» display(df1, df2, mrg)

↳    chave  valor
  0      a      1
  1      b      2
  2      c      3

↳    chave  valor
  0      a     10
  1      b     20
  2      c     30

↳    chave  valor_x  valor_y
  0      a        1       10
  1      b        2       20
  2      c        3       30

A chave usada na fusão (merge) pode estar no índice de um ou ambas as tabelas. No exemplo usamos pd.merge(esquerda, direita, left_on='chave', right_index=True) que faz a junção de esquerda.chave com direita.index

» esquerda = pd.DataFrame({'chave': ['a1', 'a1', 'a2', 'a1', 'a2', 'a3'], 'valor_1': range(6)})
» direita = pd.DataFrame({'valor_2': [50, 70]}, index=['a1', 'a2'])

» mrg = pd.merge(esquerda, direita, left_on='chave', right_index=True)

» # exibindo dataframes e sua junção, usando o índice da tabela à direita
» display(esquerda, direita, mrg)

↳    chave   valor_1
  0     a1         0
  1     a1         1
  2     a2         2
  3     a1         3
  4     a2         4
  5     a3         5

↳    valor_2
  a1      50
  a2      70

↳    chave   valor_1    valor_2
  0     a1         0         50
  1     a1         1         50
  3     a1         3         50
  2     a2         2         70
  4     a2         4         70

» # se os dataframes forem invertidos conseguiríamos o
» # mesmo resultado, exceto pela ordem das colunas, usando:
» # pd.merge(direita, esquerda, right_on='chave', left_index=True)

Junções com join()

Junções podem ser feitas com dataframe.join(dfOutro) que, por default, faz a união outer join usando o índice como chave. Esse método tem a seguinte assinatura, onde os parâmetros são
dataframe.join(dfOutro, on, how, lsuffix, rsuffix, sort),
Todos os parâmetros são opcionais exceto dfOutro. Os defaults estão em negrito.

dfOutro DataFrame, Series ou lista de DataFrames.
on string, especifica em que chave(s) fazer a junção
how strings: left, right, outer, inner. Especifica o tipo de junção.
lsuffix/rsuffix Default = ”. String a concatenar à esquerda/direita em colunas com mesmo nome.
sort False/True. Se True ordena o dataframe pela chave de junção.
» # dataframe join
» df1 = pd.DataFrame({'nome': ['Paulo', 'Maria', 'Julio','Marta'],
                       'idade': [35, 43, 31, 56]})
» df2 = pd.DataFrame({'profissao': ['médico', 'engenheiro', 'advogado']})

» df1
↳      nome      idade
  0    Paulo     35
  1    Maria     43
  2    Julio     31
  3    Marta     56

» df2
↳      profissao
  0    médico
  1    engenheiro
  2    advogado

» df1.join(df2, on=df1.index,  lsuffix='_1', rsuffix='_2') # , how = 'left' (default)
↳      nome   idade_1    profissao    idade_2
  0   Paulo        35       médico       35.0
  1   Maria        43   engenheiro       40.0
  2   Julio        31     advogado       31.0
  3   Marta        56          NaN        NaN

» # um inner join
» df1.join(df2, lsuffix='_', how='inner')
↳      nome    idade_    profissao   idade
  0   Paulo       35        médico      35
  1   Maria       43    engenheiro      40
  2   Julio       31      advogado      31

Vários dataframes podem ser concatenados de uma vez. Para isso eles devem ter dimensões compatíveis.

» # Vários dataframes podem ser concatenados
» df1 = pd.DataFrame([[23, 83], [93, 10], [73, 89], [68, 90]],
»                    index=['a', 'b', 'e', 'f'],
»                    columns=['A', 'B'])

» df2 = pd.DataFrame([[2, 8], [9, 1], [7, 8], [6, 9]],
»                    index=['a', 'b', 'c', 'd'],
»                    columns=['C', 'D'])

» df3 = pd.DataFrame([[3, 3], [3, 0], [3, 9], [8, 0]],
»                    index=['a', 'c', 'd', 'e'],
»                    columns=['E', 'F'])

» # exibe os 3 dataframes
» display(df1, df2, df3)

↳ A    B
  a    23    83
  b    93    10
  e    73    89
  f    68    90

↳ C    D
  a    2    8
  b    9    1
  c    7    8
  d    6    9
  
↳ E    F
  a    3    3
  c    3    0
  d    3    9
  e    8    0

» # exibe a junção dos dataframes
» df1.join([df2, df3])

↳         A       B      C      D      E      F
  a    23.0    83.0    2.0    8.0    3.0    3.0
  b    93.0    10.0    9.0    1.0    NaN    NaN
  e    73.0    89.0    NaN    NaN    8.0    0.0
  f    68.0    90.0    NaN    NaN    NaN    NaN

Como sempre, campos não fornecidos são preenchidos por NaN. Por ex.: df1.join([df2, df3]).loc['f', 'F'] = NaN.

concatenate()

Podemos concatenar numpy.arrays, Series e dataframes ao longo do eixo desejado.

» # Concatenando um array ao longo de um eixo
» # criamos 2 arrays
» arr1 = np.arange(6).reshape((3, 2))

» arr1
↳ array([[0, 1],
         [2, 3],
         [4, 5]])

» # concatenando arr1 consigo mesmo, ao longo de colunas
» np.concatenate([arr1, arr1], axis=1)
↳ array([[0, 1, 0, 1],
         [2, 3, 2, 3],
         [4, 5, 4, 5]])

» # concatenando arr1 consigo mesmo, ao longo de linhas
» np.concatenate([arr1, arr1], axis=0)
↳ array([[0, 1],
         [2, 3],
         [4, 5],
         [0, 1],
         [2, 3],
         [4, 5]])

» # defina outro array, com shape (3, 1)
» arr2 = np.array([[0], [1], [2]])

» arr2
↳ array([[0],
         [1],
         [2]])

» # concatenando arr1 2 arr2 pelas colunas
» np.concatenate([arr1, arr2], axis=1)

↳ array([[0, 1, 0],
         [2, 3, 1],
         [4, 5, 2]])

» # (tentando) concatenar arr1 2 arr2 pelas linhas
» np.concatenate([arr1, arr2], axis=0)
↳ ValueError: all the input array dimensions for the concatenation axis must match exactly,
  but along dimension 1, the array at index 0 has size 2 and the array at index 1 has size 1


Vemos que podemos concatenar uma matriz coluna (3 × 1) com outra matriz (3 × 2) pelas colunas, mas não pelas linhas pois as dimensãos são incompatíveis.

combine() e combine_first()

O método df1.combine(df2, func, fill_value=None, overwrite=True) combina df1 e df2, coluna a coluna, aplicando func para decidir qual valor será usado.

Podemos criar uma função que receba duas colunas e realize alguma operação entre elas, retornando outra coluna. No ex., a função f faz a soma dos elementos de duas colunas e retorna aquela com menor soma. A função g seleciona, a cada linha, qual é o maior elemento. Quando o parâmetro fill_value=r é usado todos os valores NaN são substituídos por r antes de serem submetidos à função func, exceto se ambos os valores forem nulos, quando não existirá substituição.

» df1 = pd.DataFrame({'A': [0, 3], 'B': [7, 2]})
» df2 = pd.DataFrame({'A': [2, 6], 'B': [1, 3]})

» df1
↳      A    B
  0    0    7
  1    3    2

» df2
↳      A    B
  0    2    1
  1    6    3

» # a função de comparação pode ser
» def f(x,y):
»     if x.sum() < y.sum():
»         return x
»     else:
»         return y

» # a combinação, usando essa função
» df1.combine(df2, f)
↳      A    B
  0    0    1
  1    3    3

» # O mesmo resultado pode ser obtido com uma função lambda
» df1.combine(df2, lambda x, y: x if x.sum() < y.sum() else y)

» # funções mais complexas podem ser usadas
» df1.combine(df2, lambda x, y: (x+y)*(y-x))
↳       A     B
  0     4   -48
  1    27     5

» # outro exemplo, selecionar o maior elemento de cada df
» def g(x,y):
»     a = x[0] if x[0] > y[0] else y[0]
»     b = x[1] if x[1] > y[1] else y[1]
»     return pd.Series([a,b])

» df1.combine(df2,g)
↳      A    B
  0    2    7
  1    6    3

» # o mesmo poderia ser feito com uma funlão lambda
» maior = lambda x,y: pd.Series([x[0] if x[0] > y[0] else y[0],
                                x[1] if x[1] > y[1] else y[1]])
» df1.combine(df2,maior) # mesmo output
 
» # uso de fill_value
» df1 = pd.DataFrame({'A': [0, 0], 'B': [np.NaN, 4]})
» df2 = pd.DataFrame({'A': [1, 1], 'B': [3, 3]})

» df1.combine(df2, maior, fill_value=6)
↳      A      B
  0    1    6.0
  1    1    4.0

Já o método dataframe.combine_first(dfOutro) substitui os valores NaN no dataframe com os valores de dfOutro, quando esses valores existirem.

» df1 = pd.DataFrame({'a': [1, np.nan, 5, np.nan],
»                     'b': [np.nan, 2, np.nan, 6],
»                     'c': range(2, 18, 4)})
» df2 = pd.DataFrame({'a': [5, 4, np.nan, 3, 7],
»                     'b': [np.nan, 3, 4, 6, 8]})
» display(df1, df2)
↳        a      b     c
  0    1.0    NaN     2
  1    NaN    2.0     6
  2    5.0    NaN    10
  3    NaN    6.0    14
  
↳        a      b
  0    5.0    NaN
  1    4.0    3.0
  2    NaN    4.0
  3    3.0    6.0

» df1.combine_first(df2)
↳        a      b      c
  0    1.0    NaN    2.0
  1    4.0    2.0    6.0
  2    5.0    4.0   10.0
  3    3.0    6.0   14.0
  4    7.0    8.0    NaN

Bibliografia

Consulte bibliografia completa em Pandas, Introdução neste site.

Nesse site:

Dataframes, preparação de dados


Preparação de dados

Programadores que lidam com análise de dados passam grande parte do tempo dedicado a um projeto preparando esses dados, antes mesmo de começar qualquer análise. Normalmente os dados são importados de uma fonte externa, tal como um arquivo em forma tabular em html, pdf, texto puro ou csv. Eles precisam ser convertidos para um formato legível e muitas vezes contém erros e valores ausentes. A vezes o próprio processo de conversão introduz perda de dados, tal como acontece em textos impressos transformados em texto digital por OCR (optical character recognition ). Seja qual for a origem dos dados algum trabalho de depuração deve ser feito. Em seguida eles devem passar por formatação adequada, a quebra de tabelas, o estabelecimento de vínculos entre elas, etc. Pandas oferece boas ferramentas para todas essas etapas.

Dados ausentes

Já vimos que dados não presentes em alguma tabela são representados por NaN (not a number). O objeto None do python também é tratado como um valor ausente ou NA (not available). O método dropna() descarta linhas (se axis=0, default) ou colunas (se axis=1) contendo campos nulos. dropna(how='all') descarta linhas ou colunas se todos os campos forem nulos. Também podemos determinar que apenas linhas ou colunas com um número mínimo de elementos não nulos sejam mantidas, com df.dropna(thresh=n).

» import pandas as pd
» import numpy as np

» dados = pd.Series([121.45, np.nan ,32.12,42.21,51.56])
» dados
↳ 0    121.45
  1       NaN
  2     32.12
  3     42.21
  4     51.56

» dados[3]= None
» dados.isnull()
↳ 0    False
  1     True
  2    False
  3     True
  4    False

» dados.dropna()                   # o mesmo que dados[dados.notnull()]
↳ 0    121.45
  2     32.12
  4     51.56

» from numpy import nan as NA     # para estabelecer um alias curto para np.nan
» data = pd.DataFrame([[1., 6.5, 3.9], [1.3, NA, NA], [NA, NA, NA], [NA, 5.8, 6.7]])
» data
↳        0      1      2
  0    1.0    6.5    3.9
  1    1.3    NaN    NaN
  2    NaN    NaN    NaN
  3    NaN    5.8    6.7

» data.dropna()
↳        0      1      2
  0    1.0    6.5    3.9

» data.dropna(how='all')
↳        0      1      2
  0    1.0    6.5    3.9
  1    1.3    NaN    NaN
  3    NaN    5.8    6.7

» data[4] = NA
» data
↳        0      1      2      4
  0    1.0    6.5    3.9    NaN
  1    1.3    NaN    NaN    NaN
  2    NaN    NaN    NaN    NaN
  3    NaN    5.8    6.7    NaN

» data.dropna(axis=1, how='all')
↳        0      1      2
  0    1.0    6.5    3.9
  1    1.3    NaN    NaN
  2    NaN    NaN    NaN
  3    NaN    5.8    6.7

Preenchendo valores ausentes

A invés de descartar linhas e colunas com campos ausentes podemos preencher estas lacunas. df.fillna(const) substitui campos NA com o valor único const. Um dicionário {coluna:valor} pode ser passado contendo constantes diferentes para cada coluna. Observando que df.mean() retorna uma Series com as médias de cada colunas, podemos usar df.fillna(df.mean()) para preencer NAs de cada coluna com essa média. Também podemos passar o parâmetro df.fillna(method='ffill') para preencher cada NA com o valor que o antecede na coluna. df.fillna(method='bfill') preenche NAs com o valor que o segue.

» # criando um df de teste com campos NA
» df = pd.DataFrame(np.random.randn(4, 3))
» df.iloc[0:3, 1] = NA
» df.iloc[1:3, 2] = NA

» df
↳             0           1            2
  0    0.615016         NaN    -0.860821
  1    1.195041         NaN          NaN
  2   -0.110482         NaN          NaN
  3    1.837690    1.569459     0.891858

» # preenche NAs com 0
» df.fillna(0)
↳             0           1           2
  0    0.615016    0.000000   -0.860821
  1    1.195041    0.000000    0.000000
  2   -0.110482    0.000000    0.000000
  3    1.837690    1.569459    0.891858

» # preenche coluna 1 com 10, coluna 2 com 20
» df.fillna({1:10, 2:20})
↳             0            1            2
  0    0.615016    10.000000    -0.860821
  1    1.195041    10.000000    20.000000
  2   -0.110482    10.000000    20.000000
  3    1.837690     1.569459     0.891858

» df.fillna(method='ffill')
↳             0          1            2
  0    0.615016        NaN    -0.860821
  1    1.195041        NaN    -0.860821
  2   -0.110482        NaN    -0.860821
  3    1.837690   1.569459     0.891858

» df.fillna(method='bfill')
↳             0           1           2
  0    0.615016    1.569459   -0.860821
  1    1.195041    1.569459    0.891858
  2   -0.110482    1.569459    0.891858
  3    1.837690    1.569459    0.891858

» df.fillna(method='bfill', limit=2)
» df.mean()
↳ 0    0.884316
  1    1.569459
  2    0.015519

» df.fillna(df.mean())
↳             0           1           2
  0    0.615016         NaN   -0.860821
  1    1.195041    1.569459    0.891858
  2   -0.110482    1.569459    0.891858
  3    1.837690    1.569459    0.891858

Vemos que df.fillna(method='ffill') não substituiu valores nas linhas 0, 1, 2 da coluna 1 pois nenhum valor os antecede. Nesse caso teríamos que usar method='bfill', ou outra forma de preencher o campo vazio.

Substituições com dataframe.replace()

O método df.replace() substitui valores específicos em uma Series ou dataframe. Por ex., suponha que temos uma Series de valores positivos e a inserção de negativos foi convencionada para indicar valores ausentes. Podemos alterar esses valores usando df.replace(), lembrando que nenhuma das formas abaixo altera a Serie original, a menos que inplace=True seja usado.

» serie = pd.Series([12,-2, 34, -1])
» serie
↳ 0    12
  1    -2
  2    34
  3    -1

» serie.replace(-2, -90)
↳ 0    12
  1   -90
  2    34
  3    -1

» serie.replace([-2,-1], [20,10])
↳ 0    12
  1    20
  2    34
  3    10

» serie.replace(-1, NA)
↳ 0    12.0
  1    -2.0
  2    34.0
  3     NaN

Claro que df.replace() pode ser usado para substituir um valor específico por valores calculados, usando métodos mais sofisticados de avaliação.

Em um dataframe df.replace(lista1, lista2) pode ser usado para substituir valores da lista1 pelos da lista2 (que deve ter o mesmo tamanho). df.replace(lista, escalar) substitui todos os valores em lista pelo escalar e df.replace(dicionario) substitui as chaves pelas valores no dicionário.

» df = pd.DataFrame({'a':[9,56,67], 'b':[33,55,66], 'c':[63,69,67], 'd':[2,3,9]})
» df
↳       a     b     c    d
  0     9    33    63    2
  1    56    55    69    3
  2    67    66    67    9

» df.replace(9, 100)
↳        a     b     c     d
  0    100    33    63     2
  1     56    55    69     3
  2     67    66    67   100

» df.replace([9, 55, 67], 0)
↳      a     b     c    d
  0    0    33    63    2
  1   56     0    69    3
  2    0    66     0    0

» df.replace([9, 55, 67], [1,2,3])
↳      a     b     c    d
  0    1    33    63    2
  1   56     2    69    3
  2    3    66     3    1

» df.replace({9:-9, 33:-33})
↳       a      b     c    d
  0    -9    -33    63    2
  1    56     55    69    3
  2    67     66    67   -9

Análise de outliers

Em qualquer processo de tomada de medidas ou coleta de dados existem restrições à precisão obtida. Mas, além da precisão restrita, é frequente existirem dados muito fora de qualquer curva esperada. Esses são os chamados pontos fora da curva ou outliers e geralmente são descartados. Os critérios de decisão sobre quais pontos são outliers dependem do modelo que se quer tratar.

No pandas podemos encontrar valores que estão acima ou abaixo de um certo limite.

Lembrando que np.random.randn(M, p) retorna um array de p colunas, cada uma com M valores, retirados aleatoriamente de uma distribuição normal com média 0 e variância 1, começamos por coletar um dataframe para testes.

Considerando os máximos e mínimos exibidos, vamos estabelecer arbitrariamente que valores afastados acima de 3 da média do conjunto são outliers. Isso quer dizer que consideraremos os pontos com |x| > 3 como outliers (onde |x| significa valor absoluto de x). Uma das possibilidades consiste em substituir valores não aceitáveis por np.nan e depois usar uma das formas de fill para preencher esses campos.

» dados = pd.DataFrame(np.random.randn(1000, 4))
» # são os valores mínimo e máximo desse dataframe
» dados.min().min(), dados.max().max()
↳ (-3.7113843289590496, 3.480659301328407)

» # substituimos |x| > 3 por np.nan
» dados[np.abs(dados) > 3] = np.nan
» dados.describe()    # (1) visualização do dataframe (alguns campos exibidos)
↳                   0             1             2            3
  count    996.000000    997.000000    998.000000   996.000000
  mean       0.086548      0.021479     -0.046291     0.019611
  min       -2.772219     -2.860741     -2.763174    -2.644022
  max        2.763864      2.849207      2.955914     2.905516

» dados = dados.fillna(method='bfill')
» dados.describe()    # (2) visualização do dataframe (alguns campos exibidos)
↳                    0              1              2              3
  count    1000.000000    1000.000000    1000.000000    1000.000000
  mean        0.089292       0.021845      -0.046568       0.020410
  min        -2.772219      -2.860741      -2.763174      -2.644022
  max         2.763864       2.849207       2.955914       2.905516

No primeiro uso de describe a contagem count mostra que existem linhas com campos nulos para cada coluna. Após a operação de fill todos os campos são numéricos.

Removendo linhas duplicadas

Para remover linhas duplicadas em um dataframe usamos df.drop_duplicates(). Valores duplicados em apenas uma coluna podem ser removidos com df.drop_duplicates('nomeColuna'), ou em várias colunas, passando-se uma lista df.drop_duplicates(['col1',..., 'coln']). Por default a primeira linhas, entre as duplicadas é mantida. Para manter a última usamos df.drop_duplicates('coluna', keep='last').

» # remoção de linhas duplicadas
» dic ={'col1': ['vaca', 'vaca', 'pato','pato'], 'col2': [1, 3, 4, 4]} 
» df = pd.dfFrame(dic)
» df
↳      col1    col2
  0    vaca    1
  1    vaca    3
  2    pato    4
  3    pato    4

» df.duplicated()           # retorna uma Series mostrando linhas duplicadas
↳ 0    False
  1    False
  2    False
  3     True

» df.drop_duplicates()
↳      col1   col2
  0    vaca      1
  1    vaca      3
  2    pato      4

» df.drop_duplicates('col1')
↳      col1  col2
  0    vaca     1
  2    pato     4

» df.drop_duplicates('col1', keep='last')
↳      col1   col2
  1    vaca     3
  3    pato     4

No atual estado de Pandas não é possível fazer a remoção de duplicadas sobre colunas. Para isso obtenha a transposta do dataframe, remova linhas duplicadas e o transponha novamente.

Transformações sobre elementos de um dataframe

Um restaurante faz uma lista de aquisição de produtos, descrevendo o ítem e quantas unidades devem se adquiridas.

» compra = {'produto':['leite', 'manteiga', 'laranja', 'arroz'],
            'quantos':[15, 40,50, 30]} 
» dfComprar = pd.DataFrame(compra)
» dfComprar
↳      produto  quantos
  0      leite       15
  1   manteiga       40
  2    laranja       50
  3      arroz       30

Mais tarde o gerente pede que os produtos sejam classificados como veganos ou não. Para isso podemos usar o método Series.map(dict) que transforma cada elemento usando-o como chave e retornando o valor no dicionário. Construímos um mapeamento entre produto e S/N, conforme o produto seja ou não vegano.

» veg = {'leite':'N', 'manteiga':'N', 'laranja':'S', 'arroz':'S'}
» # dfComprar['produto'] é uma Series e
» dfComprar['produto'].map(lambda x: vegano[x])
↳ 0    N
  1    N
  2    S
  3    S

» # inserindo esse serie em uma nova coluna do df
» dfComprar['vegano'] = dfComprar['produto'].map(veg)
» dfComprar
↳      produto   quantos   vegano
  0      leite        15        N
  1   manteiga        40        N
  2    laranja        50        S
  3      arroz        30        S

» # o mesmo resultado seria obtido com a função lambda 
» dfComprar['vegano']=dfComprar['produto'].map(lambda x: vegano[x])

Compartimentação e discretização

Compartimentação e discretização, (Binning e Discretization ) é o processo de particionamento de dados em faixas especificadas. Os compartimentos (faixas ou bins) são representados por variáveis categóricas, que são variáveis que podem assumir apenas um número discreto e limitado de valores, geralmente fixo. Elas estão associadas à propriedades qualitivas do sistema que se observa e podem satisfazer ou não algum critério de ordenamento.

Por ex., suponha que temos um estudo de qualquer natureza centrada sobre indivíduos onde o sexo e a faixa etária são relevantes para as conclusões que se procura obter. O sexo dos indivíduos (digamos que divididos em F = feminino, M = masculino, O = outros) não pode ser ordenado. Mas as faixas etárias são ordenáveis. Dividimos a população estudada em faixas ou bins. Sabendo que todos os participantes são maiores de idade e nenhum tem mais de 98 anos de idade usamos as faixas separadas pelas idades: 18, 34, 50, 66, 82, 98 anos.

» faixas = [18, 34, 50, 66, 82, 98]                            # definição dos intervalos de idade
» idades = [25, 18, 59, 39, 68, 26, 73, 63, 56, 84]            # idade dos indivíduos no estudo
» categorias = pd.cut(idades, faixas)
» categorias
↳ [(18.0, 34.0], NaN, (50.0, 66.0], (34.0, 50.0], (66.0, 82.0], (18.0, 34.0],
   (66.0, 82.0], (50.0, 66.0], (50.0, 66.0], (82.0, 98.0]]
  Categories (5, interval[int64]): [(18, 34] < (34, 50] < (50, 66] < (66, 82] < (82, 98]]

» # o objeto categorias é do tipo Categorical
» type(categorias)
↳ pandas.core.arrays.categorical.Categorical

» categorias.categories
↳ IntervalIndex([(18, 34], (34, 50], (50, 66], (66, 82], (82, 98]],
                closed='right', dtype='interval[int64]')

# O método pd.value_counts(categorias) fornece uma contagem para cada valor existente:
» pd.value_counts(categorias)
↳ (50, 66]    3
  (18, 34]    2
  (66, 82]    2
  (34, 50]    1
  (82, 98]    1

» # as colunas são formadas por
» pd.value_counts(categorias).index[0],  pd.value_counts(categorias)[0]
↳ (Interval(50, 66, closed='right'), 3)

» nomes_faixas = ['garoto','adulto','semi-novo','vô','matusa']
» categorias = pd.cut(idades, faixas, labels=nomes_faixas)
» categorias
↳  ['garoto', NaN, 'semi-novo', 'adulto', 'vô', 'garoto', 'vô', 'semi-novo', 'semi-novo', 'matusa']
   Categories (5, object): ['garoto' < 'adulto' < 'semi-novo' < 'vô' < 'matusa']
» pd.value_counts(categorias)
↳ semi-novo    3
  garoto       2
  vô           2
  adulto       1
  matusa       1

» # podemos transformar esse objeto em um dataframe
» dfCont = pd.DataFrame(pd.value_counts(categorias))

» # reordenar índices
» dfConf = dfCont.reindex(['garoto', 'adulto', 'semi-novo', 'vô', 'matusa'])
» dfConf
↳             0
  garoto      2
  adulto      1
  semi-novo   3
  vô          2
  matusa      1

As faixas numéricas são estabelecidas em intervalos do tipo (a, b] < (b, c] … representando intervalos abertos no limite inferior e fechados no superior. Isso significa que a não está no primeiro intervalo, mas b está. Para alterar esse comportamento usamos o parâmetro pandas.cut(...,right=False).

Podemos informar em quantas faixas queremos dividir os dados, ao invés de passar explicitamente essas faixas. Nesse caso o método pandas.cut(dados, n, precision=p) calculará n intervalos iguais baseados nos valores máximos e mínimos dos dados. precision=p determina a precisão decimal das faixas.

» # array com 20 numeros aleatórios    
» dados = np.random.rand(20)*10

» dados.min(), dados.max()         # valores mínimo e máximo
↳ (1.0012658194039414, 9.799331139583924)

» # 3 faixas (bins)
» picado = pd.cut(dados, 3, precision=2)
» pd.value_counts(picado)
↳ (6.87, 9.8]     10
  (0.99, 3.93]     7
  (3.93, 6.87]     3

Para distribuir dados em faixas baseadas em quantis usamos o método pandas.qcut(dados, n), onde n é o número de partes na partição. Intervalos de quantis customizados podem ser conseguidos passando-se uma lista em pandas.qcut(dados, lista).

» data = np.random.randn(1000)          # 1000 números aleatórios
» categorias = pd.qcut(data, 4)         # distribui em quartis
» pd.value_counts(categorias)
↳ (-3.0309999999999997, -0.683]    250
  (-0.683, 0.0106]                 250
  (0.0106, 0.702]                  250
  (0.702, 3.196]                   250

» # intervalos de quantis customizados
» pd.value_counts(pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.]))
↳ (-1.223, 0.0106]                 400
  (0.0106, 1.301]                  400
  (-3.0309999999999997, -1.223]    100
  (1.301, 3.196]                   100

Permutações aleatórias

Permutações entre as linhas (ou colunas) de um dataframe são obtidas com dataframe.take(arr), onde arr é um array com a ordem dos índices desejada. Se essa ordem for “sorteada” o dataframe fica com linhas em ordem “aleatoria”. Para reordenar colunas usamos axis=1. dataframe.sample(n) seleciona n linhas do dataframe, sem repetições (n < dataframe.shape[0]) e dataframe.sample(n, replace=True) retorna n linhas do dataframe que podem ser repetidas (como em um sorteio com reposição dos elementos sorteados).

» # dataframe de teste    
» df = pd.DataFrame(np.arange(16).reshape((4, 4)))
» df
↳      0    1    2    3
  0    0    1    2    3
  1    4    5    6    7
  2    8    9   10   11
  3   12   13   14   15

» sorteio = np.random.permutation(4)   # permutação aleatória de 0, 1, 2 e 3
» sorteio
↳ array([3, 2, 0, 1])

» df.take(sorteio)                     # dataframe na ordem de linhas sorteadas
↳       0     1     2     3
  3    12    13    14    15
  2     8     9    10    11
  0     0     1     2     3
  1     4     5     6     7

» df.take(sorteio, axis=1)             # dataframe na ordem de colunas sorteadas
↳       3     2     0     1
  0     3     2     0     1
  1     7     6     4     5
  2    11    10     8     9
  3    15    14    12    13

» df.sample(n=2)                       # 2 linhas selecionadas aleatoriamente
↳       0     1     2     3
  1     4     5     6     7
  3    12    13    14    15

» df.sample(n=2, axis=1)               # 2 colunas selecionadas aleatoriamente
↳      3    1
  0    3    1
  1    7    5
  2   11    9
  3   15   13

» df.sample(n=4, replace=True)        # 4 linhas selecionadas aleatoriamente, com reposição
↳      0    1    2    3
  0    0    1    2    3
  0    0    1    2    3
  1    4    5    6    7
  0    0    1    2    3

O mesmo dataframe obtido com df.take(sorteio) poderia ser conseguido com df.iloc[sorteio].

Indicador de computação, variáveis fictícias

Na estatística, econometria e aprendizado de máquina uma variável fictícia (variável dummy ) é uma representação de um efeito categórigo assumindo apenas os valores 0 ou 1 para indicar presença ou ausência de alguma forma de caracterização. Elas podem ser consideradas como representações numéricas de aspectos qualitativos. Um exemplo simples seria a representação da classificação de uma conta bancária como poupança (0) ou conta corrente (1).

Uma variável categórica pode ser transformada em uma matriz dummy ou de indicadores. Se uma series (uma coluna de um dataframe) possui p valores distintos podemos obter um dataframe com o mesmo número de colunas, cada uma contendo apenas 0 ou 1. Para isso usamos o método pandas.get_dummies(Series) que retorna um dataframe marcando as posições onde cada um dos p valores ocorrem. Um prefixo pode ser acrescentado aos nomes das colunas com pandas.get_dummies(Series, prefix='p').

Por ex., em uma pesquisa foi marcado, para cada indivíduo participante, o campo sexo = F (feminino), M (masculino), O (outros).

» df = pd.DataFrame({'individuo': ['Fulano', 'Beltrano', 'Cicrano', 'Deltrano', 'Cruciano', 'Marciano'],
                     'sexo': ['H','H','H','F','O','F']})
» df
↳      individuo   sexo
  0       Fulano      H
  1     Beltrano      H
  2      Cicrano      H
  3     Deltrano      F
  4     Cruciano      O
  5     Marciano      F

» # categorizando a coluna 'sexo'
» pd.get_dummies(df['sexo'])
↳      F    H    O
  0    0    1    0
  1    0    1    0
  2    0    1    0
  3    1    0    0
  4    0    0    1
  5    1    0    0

» # inserindo um prefixo (no nome das colunas)
» pd.get_dummies(df['sexo'], prefix='sexo').head(2)
↳    sexo_F  sexo_H  sexo_O
   0      0       1       0
   1      0       1       0

Muitas vezes os dados devem ser manipulados e preparados para uma devida categorização. Suponha que temos uma lista de autores, cada um associado a um ou mais gêneros literários separados por |. Queremos uma listagem de autores versus gêneros, marcando em qual gênero cada um escreve.

» # importamos de qualquer fonte o seguinte dataframe:
» dfAutores
↳       autor                genero
  0   Antonio          poesia|conto
  1      José               romance
  2     Marco      ficção|biografia
  3     Pedro          poesia|conto

» # cada autor está associado a um ou mais gêneros
» genero = dfAutores.genero
» autores = dfAutores.autor
» # as duas séries têm o mesmo comprimento (len(autores) = len(generos) = 4, 4

Criamos uma lista vazia e a preenchemos com todos os gêneros, quebrando os campos em |. Depois usamos pandas.unique(lista) para conseguir um array com os gêneros, sem repetições, como em um conjunto (set).

» lista = []
» for t in genero:
»     lista.extend(t.split('|'))
» unicos = pd.unique(lista)
» unicos
↳ array(['poesia', 'conto', 'romance', 'ficção', 'biografia'], dtype=object)

Em seguida criamos um dataframe de zeros com os autores nas colunas e gêneros nas linhas.

» dfZero = pd.DataFrame(np.zeros((len(unicos),len(autores))), index=unicos, columns=autores).astype(int)
» dfZero          # estado inicial de dfZero
↳ autor    Antonio    José    Marco    Pedro
  poesia         0       0        0        0
  conto          0       0        0        0
  romance        0       0        0        0
  ficção         0       0        0        0
  biografia      0       0        0        0

# preenchemos esse dataframe
» for i in range(len(unicos)):
»     for k in range(len(genero)):
»         if unicos[i] in genero[k]:
»             dfZero.iloc[i,k] = 1
            
» dfZero    # estado final de dfZero
↳ autor    Antonio    José    Marco    Pedro
  poesia         1       0        0        1
  conto          1       0        0        1
  romance        0       1        0        0
  ficção         0       0        1        0
  biografia      0       0        1        0

O duplo loop sobre a lista de gêneros únicos, unicos, e a lista original de gêneros genero faz a verificação se um dos generos está em genero1|genero2…. Por exemplo, na linha 3, coluna 2 temos:

» unicos[3], genero[2],  unicos[3] in genero[2]
↳ ('ficção', 'ficção|biografia', True)

Se o resultado é verdadeiro o dataframe terá o campo correspondente trocado para 1. Os demais permanecem com o valor 0. O dataframe final é o resultado desejado.

Tratamento de campos de texto

Operações com strings são também vetorializadas no pandas. No ex. usamos os códigos telefones dos países: 55-Brasil, 47-Noruega, 52-México. Construímos duas séries e as concatenamos em um dataframe, df = pd.concat([serie, srPais], axis=1).

» lista = ['055-11-12345678', '047-21-87654321', '055-11-13579135', '052-78-45665412']
» serie = pd.Series(lista)
» serie
↳ 0    055-11-12345678
  1    047-21-87654321
  2    055-11-13579135
  3    052-78-45665412

» # booleano, linhas que contém '-11-'
» serie.str.contains('-11-')
↳ 0     True
  1    False
  2     True
  3    False

» # linhas que contém '-11-'
» serie[serie.str.contains('-11-')]
↳ 0    055-11-12345678
  2    055-11-13579135

» # lista com os códigos dos países
» codigos = [x.split('-')[0] for x in lista]
» codigos
↳ ['055', '047', '055', '052']

» # dicionário para conversão código ⇒ país
» pais = {'055':'Brasil', '047':'Noruega', '052':'México'}
» srPais = pd.Series([pais[x] for x in codigos])      # veja comentário †
» srPais
↳ 0     Brasil
  1    Noruega
  2     Brasil
  3     México

» # juntamos as duas series em um dataframe
» df = pd.concat([serie, srPais], axis=1)
» df = df.rename(columns = {0:'telefone', 1:'pais'})

» df
↳             telefone       pais
  0    055-11-12345678     Brasil
  1    047-21-87654321    Noruega
  2    055-11-13579135     Brasil
  3    052-78-45665412     México

» # nome do país começado com 'No'
» df[df['pais'].str.startswith('No')]
↳             telefone       pais
  1    047-21-87654321    Noruega

» # acrescenta campo com 3 primeiras letras do nome
» df['abreviado'] = df['pais'].str[:3]
» df
↳             telefone      pais    abreviado
  0    055-11-12345678    Brasil          Bra
  1    047-21-87654321   Noruega          Nor
  2    055-11-13579135    Brasil          Bra
  3    052-78-45665412    México          Méx

(): A linha srPais = pd.Series([pais[x] for x in codigos]) (uma compreensão de lista) percorre os valores em codigos e os usa como chaves no dicionário pais, retornando seus valores.

🔺Início do artigo

Bibliografia

  • McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
    O’Reilly Media, 2018.

Consulte bibliografia completa em Pandas, Introdução neste site.

Nesse site:

Pandas – Dataframes


Dataframes do pandas

Usamos a marcação:
» # linhas de comentários
» linhas de código (input)
↳ linhas de output

Um dataframe é uma forma de armazenar dados em forma tabular, como em uma planilha. O dataframe do pandas consiste em uma coleção de Series que são dispostas como suas colunas. A cada linha está associado um índice que serve para ordenar e selecionar dados. Como Series, cada coluna tem um tipo definido. No entanto, não é necessário que todas as colunas tenham o mesmo tipo e portanto dados de tipos diferentes podem ser armazenados.

O pandas usa muitos dos conceitos de programação do NumPy. A diferença principal entre eles está em que um array do NumPy usa dados homogêneos (todos do mesmo tipo) enquanto os dataframes do pandas podem conter dados de tipos diferentes.

Assim como o NumPy, muitas operações com dataframes levam em consideração o eixo ou axis. O default é axis = 0 (ou axis = 'index') o que indica operação sobre as linhas. axis = 1 (ou axis = 'column') indica operação realizada sobre as colunas.

O método mais comum de se criar um dataframe consiste em passar um dicionário e uma lista de índices para o construtor.

» import pandas as pd
» import numpy as np
» dados = {
            'nome': ['Pedro', 'Maria', 'Janaina', 'Wong', 'Roberto', 'Marco', 'Paula'],
            'cidade': ['São Paulo', 'São Paulo', 'Rio de Janeiro', 'Brasília',
                       'Salvador', 'Curitiba', 'Belo Horizonte'],
            'idade': [34, 23, 32, 43, 38, 31, 34],
            'nota': [83.0, 59.0, 86.0, 89.0, 98.0, 61.0, 44.0]
          }

» ids = [10, 11, 12, 13, 14, 15, 16]
» dfAlunos = pd.DataFrame(data=dados, index=ids)
↳ 

nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
12 Janaina Rio de Janeiro 32 86.0
13 Wong Brasília 43 89.0
14 Roberto Salvador 38 98.0
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0

No caso acima usamos um dict onde as chaves são os nomes dos campos ou colunas. À cada chave está associada uma lista cujos valores se tornam os valores das linhas, em cada coluna. A lista de índices foi fornecida separadamente. Se a lista ids não tivesse sido fornecida os índices do dataframe seriam inteiros, começando em 0.

Dataframes possuem a propriedade shape que contém as dimensões do objeto e os métodos head(n) e tail(n) que permitem, respectivamente, a visualização das n primeiras ou últimas linhas. Ao carregar um dataframe é sempre útil visualizar suas primeiras linhas e nomes de colunas. Também pode ser útil visualizar a matriz sob forma transposta, dada por dfAlunos.T.

» dfAlunos.shape
↳ (7, 4)
» # o que significa que temos 7 linhas, com 4 campos ou colunas.

» # para visualizar apenas as 2 primeiras linhas
» dfAlunos.head(2)
↳
nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
# para visualizar apenas as 2 últimas linhas
» dfAlunos.tail(2)
↳
nome cidade idade nota
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0
# A transposta:
» dfAlunos.T
↳
10 11 12 13 14 15 16
nome Pedro Maria Janaina Wong Roberto Marco Paula
cidade São Paulo São Paulo Rio de Janeiro Brasília Salvador Curitiba Belo Horizonte
idade 34 23 32 43 38 31 34
nota 83 59 86 89 98 61 44

Os nomes das colunas podem ser obtidos em uma lista, em um nome específico. Devemos nos lembrar que cada coluna do dataframe é uma Series. Portanto valem para elas os métodos e propriedades das Series.

» dfAlunos.columns
↳ Index(['nome', 'cidade', 'idade', 'nota'], dtype='object')

» # O nome da segunda coluna (lembrando que se conta a partir de 0)
» dfAlunos.columns[1]
↳ 'cidade'
» # Selecionando a coluna 'cidade'
» dfAlunos['cidade']
↳  10         São Paulo
   11         São Paulo
   12    Rio de Janeiro
   13          Brasília
   14          Salvador
   15          Curitiba
   16    Belo Horizonte
   Name: cidade, dtype: object

» # cada coluna é uma Series
» type(dfAlunos['cidade'])
↳ pandas.core.series.Series

» # os métodos das Series se aplicam
» dfAlunos['cidade'].value_counts()
↳  São Paulo         2
   Curitiba          1
   Rio de Janeiro    1
   Belo Horizonte    1
   Salvador          1
   Brasília          1
   Name: cidade, dtype: int64

» # valores únicos podem ser obtidos com unique()
» dfAlunos['cidade'].unique()
↳ array(['São Paulo', 'Rio de Janeiro', 'Brasília', 'Salvador', 'Curitiba',
         'Belo Horizonte'], dtype=object)

» # também podemos transformar esses valores em um set
» set(dfAlunos['cidade'])
↳ {'Belo Horizonte',
   'Brasília',
   'Curitiba',
   'Rio de Janeiro',
   'Salvador',
   'São Paulo'}


Observe que dfAlunos['cidade'] retorna uma Series, que é a coluna especificada do DF. Já o comando dfAlunos[['cidade']] retorna um dataframe com uma única coluna. É sempre importante saber com que tipo de objeto estamos lidando. Para isso podemos usar type() para conhecer esse tipo. Por exemplo, type(dfAlunos[['cidade']]) retorna pandas.core.frame.DataFrame . Observe que strings são listadas apenas como objects (sem discriminação de serem strings).

Também se pode usar a notação de ponto, dfAlunos.cidade, para obter a mesma coluna.

Como dissemos, o objeto DataFrame do pandas é formado por colunas que são Series, cada uma delas contendo elementos do mesmo tipo. As linhas podem, portanto, conter elementos de tipos diferentes. Para ver os tipos de cada coluna podemos examinar a propriedade dtype ou o método .info() que fornece uma visão geral sobre os dados, inclusive sobre a existência de valores nulos nos dados.

» dfAlunos.dtypes
↳ nome       object
  cidade     object
  idade       int64
  nota      float64
  dtype: object

» # Uma visão geral sobre os dados pode ser obtido com .info()
» dfAlunos.info()
↳ <class 'pandas.core.frame.DataFrame'>
  Int64Index: 7 entries, 10 to 16
  Data columns (total 4 columns):
   #   Column  Non-Null Count  Dtype
  ---  ------  --------------  -----
   0   nome    7 non-null      object
   1   cidade  7 non-null      object
   2   idade   7 non-null      int64
   3   nota    7 non-null      float64
  dtypes: float64(1), int64(1), object(2)
  memory usage: 600.0+ bytes

A descrição estatística dos campos numéricos é obtida com .describe() que fornece a contagem de itens, o valor médio, o desvio padrão, os quantis e máximos e mínimos. O método .corr() fornece o Coeficiente de Correlação de Pearson para todas as colunas numéricas da tabela. O resultado é um número no intervalo [-1, 1] que descreve a relação linear entre as variáveis.

# describe: resumo estatístico dos campos numéricos
» dfAlunos.describe()
↳
idade nota
count 7.000000 7.000000
mean 33.571429 74.285714
std 6.187545 19.661420
min 23.000000 44.000000
25% 31.500000 60.000000
50% 34.000000 83.000000
75% 36.000000 87.500000
max 43.000000 98.000000
» dfAlunos.corr()
↳ 
idade nota
idade 1.000000 0.564238
nota 0.564238 1.000000

Para acrescentar uma ou mais linhas (registros) ao dataframe podemos criar um novo dataframe com quantas linhas forem necessárias e concatená-lo com o antigo usando o método .concat().

» # criamos dataframe para a aluna Juliana e seus dados
» dfInserir = pd.DataFrame([('Juliana','Curitiba',28,80.0)],
                             columns=['nome','cidade','idade','nota'],
                             index=[100])
» pd.concat([dfAlunos, dfInserir])
↳
nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
12 Janaina Rio de Janeiro 32 86.0
13 Wong Brasília 43 89.0
14 Roberto Salvador 38 98.0
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0
100 Juliana Curitiba 28 80.0

Observe que o dataframe original não foi modificado. Caso se pretenda que modificação se torne permanente você deve atribuir o resultado retornado a uma novo (ou o mesmo) dataframe, como em dfAlunos = pd.concat([dfAlunos, dfInserir]).

Muitas vezes queremos que a novo dataframe criado ignore os índice das duas tabelas concatenadas. Nesse caso podemos ignorar os índices antigos e substituí-los por novos índices fornecidos, ou deixar que sejam preenchidos automaticamente.

» df = pd.concat([dfAlunos, dfInserir], ignore_index=True)
» df.index
↳ RangeIndex(start=0, stop=8, step=1)
» # os índices são inteiros de 0 até 8 (exclusive)

Uma nova coluna pode ser inserida, inclusive usando valores obtidos nas linhas. Na operação abaixo inserimos o campo calculado que é igual à multiplicação dos campos nota * idade, que não tem significado e é feito apenas como demonstração.

» dfAlunos['calculado']=dfAlunos['nota'] * dfAlunos['idade']
» dfAlunos
↳
nome cidade idade nota calculado
10 Pedro São Paulo 34 83.0 2822.0
11 Maria São Paulo 23 59.0 1357.0
12 Janaina Rio de Janeiro 32 86.0 2752.0
13 Wong Brasília 43 89.0 3827.0
14 Roberto Salvador 38 98.0 3724.0
15 Marco Curitiba 31 61.0 1891.0
16 Paula Belo Horizonte 34 44.0 1496.0

Como essa nova coluna não tem nenhum significado vamos apagá-la usando .drop().

# a operação seguinte retorna o dataframe sem a coluna 'calculado', mas não altera a original
» dfAlunos.drop(['calculado'], axis=1)
» # para alterar o dataframe usamos o parâmetro inplace=True
» dfAlunos.drop(['calculado'], axis=1, inplace=True)

Agora o dataframe tem a mesma estrutura de colunas original. Muitas operações do pandas retornam o resultado sobre o objeto sem alterá-lo. Algumas delas admitem o parâmetro inplace que, se True, faz a alteração do objeto in loco.

Para selecionar mais de uma coluna passamos uma lista com os nomes dos campos entre os colchetes.

» lista = ['nome','idade']
» # a linha abaixo é idêntica à dfAlunos[['nome','idade']]
» dfAlunos[lista]
↳
nome idade
10 Pedro 34
11 Maria 23
12 Janaina 32
13 Wong 43
14 Roberto 38
15 Marco 31
16 Paula 34


Podemos obter somas dos termos, tanto no sentido das linhas quanto das colunas, o que servirá como exemplo do uso do parâmetro axis. Relembrando:

axis = 0 (axis = ‘index’) opera sobre todas as linhas de cada coluna
axis = 1 (axis = ‘column’) opera sobre todas as colunas de cada linha

Para mostrar isso vamos construir um dataframe contendo apenas os dados numéricos, com os campos ‘idade’ e ‘nota’. Em seguida aplicamos sum(axis=0) para obter a soma das idades e notas, e sum(axis=1) para a soma
de cada linha.

» dfNumerico=dfAlunos[['idade', 'nota']]
» # a soma dos elementos em cada coluna
» dfNumerico.sum()   # o mesmo que dfNumerico.sum(axis=0)
↳ idade         235.0
  nota          520.0
  dtype: float64

» # a soma dos elementos em cada linha
» dfNumerico.sum(axis=1)
↳ 10    117.0
  11     82.0
  12    118.0
  13    132.0
  14    136.0
  15     92.0
  16     78.0
  dtype: float64

Importando um arquivo externo

É comum que os dados estejam inicialmente em forma de texto com os dados gravados em linhas e com valores separados por vírgula (um arquivo csv, comma separated values) ou outros separadores, tais como tabulação ou ponto e vírgula (;). Também ocorre que a exportação de outros aplicativos, como o Excel, possa ser feita nesse formato ou ser nele convertido.

Suponha que tenhamos no disco, na pasta de trabalho de sua sessão, um arquivo com o seguinte conteúdo:

    id, nome, cidade, idade, nota
    10, Pedro, São Paulo, 34, 83.0
    11, Maria, São Paulo, 23, 59.0
    12, Janaina, Rio de Janeiro, 32, 86.0
    13, Wong, Brasília, 43, 89.0
    14, Roberto, Salvador, 38, 98.0
    15, Marco, Curitiba, 31, 61.0
    16, Paula, Belo Horizonte, 34, 44.0

Não é importante que as colunas estejam bem alinhadas. Para importar esses dados para dentro de um dataframe usamos o método do pandas .read_csv(arq), onde arq é o nome completo do arquivo a ser lido (inclusive com seu caminho).

» dfNovoAlunos = pd.read_csv('./alunos.csv')
» dfNovoAlunos
↳
id nome cidade idade nota
0 10 Pedro São Paulo 34 83.0
1 11 Maria São Paulo 23 59.0
2 12 Janaina Rio de Janeiro 32 86.0
3 13 Wong Brasília 43 89.0
4 14 Roberto Salvador 38 98.0
5 15 Marco Curitiba 31 61.0
6 16 Paula Belo Horizonte 34 44.0

Vemos que o campo ‘id’ foi lido como um campo comum. Ele pode ser transformado em um pindice id efetivo por meio do método dataFrame.set_index:

» # torne o campo id o índice
» dfNovoAlunos.set_index('id', inplace=True)
» dfNovoAlunos.head(2)
↳ 
nome cidade idade nota
id
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0

Observe que o índice é apresentado na segunda linha do cabeçalho para indicar que não é um campo comum.

Alternativamente podemos ler o arquivo csv usando diretamente a primeira coluna como índice, informado pelo parâmetro index_col. Se o arquivo não contiver vírgulas separando os campos e sim outro sinal qualquer, como ; ou tabulações, passamos essa informação usando o parâmetro sep. Na última importação usamos url, a URL completa do arquivo, que pode estar em qualquer ponto disponivel da rede.

» # para usar a 1a coluna como índice
» dfNovoAlunos = pd.read_csv('./alunos.csv', index_col=0)
» # para ler arquivo em url, usando tab como separador
» dfOutroDF = pd.read_csv(url, sep='\t')

Vimos que, se nenhum argumento for passado, a primeira linha do arquivo é tomada como contendo os nomes (ou headers) das colunas. Para evitar isso passamos o parâmetro header = None. Nesse caso o nome das colunas é substituído por números.

Suponha que o arquivo nums.csv, com o conteúdo abaixo, esteja gravado no disco.

    11,12,13,14
    21,22,23,24
    31,32,33,34

Ele pode ser lido da seguinte forma:

» # informa que 1a linha não é header
» dfNone = pd.read_csv('./dados/nums.csv', header=None)

» # inserindo o nome ou labels para as colunas
» dfNames = pd.read_csv('./dados/nums.csv', names=('A', 'B', 'C', 'D'))

» # exibe os dois dataframes
» display('sem headers:', dfNone, 'com headers:', dfNames)
↳

‘sem headers:’

0 1 2 3
0 11 12 13 14
1 21 22 23 24
2 31 32 33 34

‘com headers:’

A B C D
0 11 12 13 14
1 21 22 23 24
2 31 32 33 34

Finalmente, se o cabeçalho contendo os títulos das colunas não está na primeira linha podemos passar o parâmetro header=n. A n-ésima linha será tomada como cabeçalho e todas as linhas anteriores serão ignoradas.

» dfPula2 = pd.read_csv('./dados/nums.csv', header=2)

Importação de csv com linhas de comprimentos desiguais

Pode ocorrer que um arquivo importado precise de alguma forma de tratamento para a montagem adequada de suas colunas, quando importado em um dataframe. Suponha, por exemplo, que temos em disco um arquivo de texto como o seguinte conteúdo:

palavras
a;1;2;3;6;7;9;10;17;121;131;138;252;463
aba;146
abafa;125;127;129
abaixado;1;125;127;130
abastecer;121;146;150;250;354;358
abatido;1;124;125;127;129;130;140;143;358;360
aberto;13;22;125;126;131;132;138;11;250;252
abismei;125;126
abra;14;22;125;126;131;132;138;11;250;252
abriga;125;126;131;137
acabrunha;143;150
acalmar;1;124;125;126;140;142;143;253
acaso;125;126;131;135;253

Essa é uma seleção muito pequena, para fim de demonstração da técnica, do extenso dicionário LIWC que associa palavras da língua portuguêsa à um conjunto de códigos associados à características linguísticas e cognitivas. Queremos classificar palavras de acordo com esse dataset.

Não podemos importar diretamente esses dados usando pd.read_csv(arquivo, sep=';') pois o comprimento irregular dos códigos geraria erro na importação. Então nos resta importar todas as linhas em uma coluna e tratá-las depois, de modo adequado.

# o arquivo csv está em    
arquivo='./dados/liwc.csv'
# importamos com separador que não existe no texto para obter apenas 1 coluna
dfPalavras = pd.read_csv(arquivo, sep='#')

dfPalavras
    palavras
0   a;1;2;3;6;7;9;10;17;121;131;138;252;463
1   aba;146
2   abafa;125;127;129
3   abaixado;1;125;127;130
4   abastecer;121;146;150;250;354;358
5   abatido;1;124;125;127;129;130;140;143;358;360
6   aberto;13;22;125;126;131;132;138;11;250;252
7   abismei;125;126
8   abra;14;22;125;126;131;132;138;11;250;252
9   abriga;125;126;131;137
10  acabrunha;143;150
11  acalmar;1;124;125;126;140;142;143;253
12  acaso;125;126;131;135;253

# cada linha pode se quebrada em ; retornando listas
dfPalavras.palavras.str.split(';').head(3)

0    [a, 1;2;3;6;7;9;10;17;121;131;138;252;463]
1                                    [aba, 146]
2                          [abafa, 125;127;129]

O próprio método split pode ser usado com os parâmetros str.split(sep, n, expand), que quebra a string em sep, usando apenas as primeiras n partes, e montando um dataframe se expand=True. Montamos um dataframe vazio df, separamos as linhas em 2 partes e as aplicamos em df, com as partes palavra e codigo separadas.

df = pd.DataFrame({})
df[['palavra','codigo']] = dfPalavras.palavras.str.split(';', n=1, expand=True)
# o seguinte dataframe é gerado
df
    palavra      codigo
0   a            1;2;3;6;7;9;10;17;121;131;138;252;463
1   aba          146
2   abafa        125;127;129
3   abaixado     1;125;127;130
4   abastecer    121;146;150;250;354;358
5   abatido      1;124;125;127;129;130;140;143;358;360
6   aberto       13;22;125;126;131;132;138;11;250;252
7   abismei      125;126
8   abra         14;22;125;126;131;132;138;11;250;252
9   abriga       125;126;131;137
10  acabrunha    143;150
11  acalmar      1;124;125;126;140;142;143;253
12  acaso        125;126;131;135;253

# podemos encontrar as palavras associadas ao codigo = 127
df[df.codigo.str.contains('127')]['palavra']
2       abafa
3    abaixado
5     abatido

# ou os codigos associados à palavra 'abafa'
df[df.palavra == 'abafa']['codigo'].values[0]
'125;127;129'

Na última linha df[df.palavra == 'abafa']['codigo'] é uma Series. Para extrair seu valor usamos .values[0]. Nesse caso o número de códigos é muito varíavel e não seria prático construir uma coluna para cada código.

Se for necessário ter colunas correspondendo à cada código teríamos que preencher os campos vazio com algum valor, como 0 ou NaN.

Gravando o dataframe em arquivos pickle

Ao términdo de cada fase de depuração, manipulação e ajustes do dataframe, podemos gravá-lo em disco em um arquivo pickle. O arquivo gravado pode ser lido e o dataframe recuperado em nova sessão de código, sem que tenhamos que repetir as etapas já realizadas.

» pd.to_pickle(dfNovoAlunos, './dados/Alunos.pkl')
» del dfNovoAlunos
» dfLido = pd.read_pickle('./dados/Alunos.pkl')

dfLido será um dataframe idêntico ao dfNovoAlunos gravado em etapa anterior. A pasta de destino deve existir ou uma exceção será lançada.

to_pickle Grava um objeto do pandas em arquivo pickled
read_pickle Ler arquivo pickle recuperando objeto
DataFrame.to_hdf Grava um objeto do pandas em arquivo HDF5
read_hdf Ler arquivo hdf recuperando objeto
DataFrame.to_sql Grava dataframe em um banco de dados sql
read_sql Ler arquivo sql recuperando objeto
DataFrame.to_parquet Grava dataframe em formato parquet binário.
read_parquet Ler arquivo parquet recuperando objeto
🔺Início do artigo

Bibliografia

Consulte bibliografia completa em Pandas, Introdução neste site.

Nesse site:

Data Frames

Dataframes são uma generalização de matrizes onde cada coluna pode ser de um tipo ou classe diferente de dados. Neste sentido este é o objeto de R que mais se aproxima de uma planilha ou de um banco de dados.

Um dataframe pode conter nomes para as colunas, uma indicação de que dado está nela armazenado, e também um atributo especial chamado row.names que guarda informações sobre cada uma de suas linhas. Eles podem ser criados explicitamente com a função data.frame() ou através da transformação forçada de outros tipos de objetos, como as listas. Por sua vez eles podem ser transformados em matrizes data.matrix().

Muitas vezes um dataframe é carregado diretamente através da leitura de dados gravados previamente por meio de comandos como read.table() ou read.csv().

Vamos criar um dataframe contendo os dados de alunos, contendo um id, nome, idade e menção final (II, MI, MM, MS, SS), expostos na tabela seguinte:

id Nome Idade Menção
1 Paulo 24 MM
2 Joana 23 MS
3 Marcos 19 MM
4 Fred 21 II
5 Ana 20 MI

Estes dados podem ser inseridos em um dataframe da seguinte forma:

> id <- 1:5
> nome <- c("Paulo", "Joana", "Marcos", "Fred", "Ana")
> idade <- c(24, 23, 19, 21, 20)
> mencao <- c("MM", "MS", "MM", "II", "MI" )
> alunos <- data.frame(id, nome, idade, mencao)
> alunos
  id   nome idade mencao
1  1  Paulo    24     MM
2  2  Joana    23     MS
3  3 Marcos    19     MM
4  4   Fred    21     II
5  5    Ana    20     MI
> # As entradas no frame podem ser consultadas de várias maneiras
> alunos["mencao"]
  mencao
1     MM
2     MS
3     MM
4     II
5     MI
> alunos[c("nome", "mencao")]
    nome mencao
1  Paulo     MM
2  Joana     MS
3 Marcos     MM
4   Fred     II
5    Ana     MI
> alunos$mencao
[1] MM MS MM II MI
Levels: II MI MM MS

Observe que a coluna de menções (bem como a de nomes) foi transformada em um fator, não ordenado. Em situações em que o uso de um dataframe é recorrente as funções attach(), detach() e with() podem ser bastante úteis.

A função attach() informa ao interpretador de R qual é o dataframe default a que se referem os campos citados no código. A função detach() remove esta ligação. Seu uso é opcional mas é uma boa prática de programação que deve ser seguida.

attach(alunos)
The following objects are masked _by_ .GlobalEnv:
    id, idade, mencao, nome
> id
[1] 1 2 3 4 5
> summary(idade)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   19.0    20.0    21.0    21.4    23.0    24.0 
> table(mencao)
mencao
II MI MM MS 
 1  1  2  1 
> detach(alunos)

Quando a função attach foi executada o interpretador informou que os campos id, idade, mencao, nome receberam máscaras para sua execução. Se outra variável previamente definida tinha o mesmo nome ela terá precedência na busca pelo interpretador (o que pode não ser o comportamento desejado). Para evitar tais possíveis conflitos podemos usar with(). Desta forma se garante que todos os comandos dentro das chaves se refiram à alunos.

> with(alunos, {
      print(idade)
      print(mencao)
  })
[1] 24 23 19 21 20
[1] MM MS MM II MI
Levels: II MI MM MS
> # Se um único comando será usado as chaves são opcionais
with(alunos, summary(idade))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
   19.0    20.0    21.0    21.4    23.0    24.0
> # a função summary faz uma estatística básica dos dados do argumento

> # Para que a atribuição continue existindo fora das chaves usamos <<-
> with(alunos, {
        estatistica <<- summary(idade)
        estat <- summary(idade)
  })
> estatistica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   19.0    20.0    21.0    21.4    23.0    24.0 
> estat
Error: object 'estat' not found
> # A mesma dataframe pode ser criada com a atribuição de labels
> # às suas linhas. No caso atribuímos o id a este label
> alunos <- data.frame(id, nome, idade, mencao, row.names = id)
> # Estes labels são usados para exibição de dados e gráficos

> # O dataframe pode ser ordenado por ordem de idade dos alunos
> alunos <- alunos[order(alunos$idade),]
> alunos
  id   nome idade mencao
3  3 Marcos    19     MM
5  5    Ana    20     MI
4  4   Fred    21     II
2  2  Joana    23     MS
1  1  Paulo    24     MM

> # O número de linhas e colunas podem ser obtidos
> nrow(alunos)
[1] 5
> ncol(alunos)
[1] 4

> # O data frame pode ser criado vazio, com os campos especificados
> df <- data.frame(id=numeric(0), nome=character(0), nota=numeric(0))
> df
[1] id   nome nota
<0 rows> (or 0-length row.names)
> # e editado na janela edit
> df <- edit(df)

Funções usadas com dataframes:

Função Efeito
df <- data.frame(vetor1, ... , vetorn) cria um dataframe
df[obj1] exibe a coluna relativa à obj1
attach(df) liga as variáveis ao dataframe df
detach(df) remove ligação ao dataframe df
with(df, {comandos}) executa comandos com ligação ao dataframe df
summary(obj1) retorna estatística básica dos dados de obj1
nrow(df) retorna número de linhas de df
ncow(df) retorna número de colunas de df
edit(df) abre uma janela para a edição do data frame
O operador edit() permite a edição de dados e dos nomes de campos de um data frame (ou de outros objectos de R, como vetores e matrizes).
Ele, no entanto, deixa o objeto inalterado. Para que as alterações sejam passadas para o mesmo objeto devemos fazer
obj <- edit(obj).
O mesmo resultado pode ser conseguido com o operador fix():
fix(obj) # alterações já ficam gravados em obj

Manipulando data frames com comandos SQL

Usando o pacote sqldf consultas podem ser feitas à um data frame usando consultas sql. Para isso instalamos o pacote e usamos a função sqldf(). Para exemplificar usamos o dataframe mtcars, que é carregado por padrão no R.

> # Nomes das colunas e primeiras 6 linhas de mtcars:
> head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

> install.packages("sqldf") # instala pacote
> library(sqldf)
Loading required package: gsubfn
Loading required package: proto
> sql <- "SELECT * FROM mtcars WHERE gear=3 AND carb>2 ORDER BY mpg"
> carros <- sqldf(sql, row.names=TRUE)
> carros
                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Leia mais sobre a leitura e manipulação de arquivos *.csv na sessão Aquisição de Dados.

Também é possível construir um data.frame aplicando uma consulta sql sobre um arquivo *.csv gravado no disco. Suponha que o seguinte conteúdo esteja gravado em um arquivo alunos.csv.

   id, Nome,  Sobrenome, Idade, Sexo
   1,  Marta, Rocha,     24,    F
   2,  Pedro, Souza,     12,    M
   3,  José,  Marciano,  15,    M
   4,  Joana, Santos,    21,    F
   5,  Lucas, Pereira,   20,    M

O código seguinte usa a função, read.csv.sql() para selecionar o nome e a idade dos alunos com idade superior a 20 anos, de dentro do arquivo.

> sql <- "SELECT Nome, Idade FROM alunos.csv WHERE Idade>20"
> rs <- read.csv.sql("alunos.csv", sql)
> # O seguinte data frame fica carregado: 
> rs
     Nome Idade
1   Marta    24
2   Joana    21


Operadores e Operações