Mostrando postagens com marcador doc pandas. Mostrar todas as postagens
Mostrando postagens com marcador doc pandas. Mostrar todas as postagens

segunda-feira, 5 de abril de 2021

Como criar gráficos com pandas?

In [1]: import pandas as pd

In [2]: import matplotlib.pyplot as plt
  • Dados de qualidade do ar

    Para este tutorial, são usados dados de qualidade do ar sobre o NO2, disponibilizados pela openaq e usando o pacote py-openaq. O conjunto de dados air_quality_no2.csv fornece valores de NO2 para as estações de medição FR04014, BETR801 e London Westminster em Paris, Antuérpia e Londres, respectivamente.

    O arquivo csv usado nesse post pode ser baixado aqui.

    In [3]: air_quality = pd.read_csv("data/air_quality_no2.csv", index_col=0, parse_dates=True)
    
    In [4]: air_quality.head()
    Out[4]: 
                         station_antwerp  station_paris  station_london
    datetime                                                           
    2019-05-07 02:00:00              NaN            NaN            23.0
    2019-05-07 03:00:00             50.5           25.0            19.0
    2019-05-07 04:00:00             45.0           27.7            19.0
    2019-05-07 05:00:00              NaN           50.4            16.0
    2019-05-07 06:00:00              NaN           61.9             NaN
    

    Observação

    Usamos os parâmetros index_col e parse_dates da função read_csv para definir a primeira (0ª) coluna como índice do DataFrame resultante e converter as datas na coluna em objetos Timestamp, respectivamente.

  • Quero uma verificação visual rápida dos dados.

    In [5]: air_quality.plot()
    Out[5]: <AxesSubplot:xlabel='datetime'>
    

    Com o DataFrame, o pandas cria por padrão um gráfico de linha para cada uma das colunas com dados numéricos.

  • Quero mostrar apenas as colunas da tabela de dados com os dados de Paris.

    In [6]: air_quality["station_paris"].plot()
    Out[6]: <AxesSubplot:xlabel='datetime'>
    

    Para mostrar uma coluna específica, use o método de seleção do tutorial de dados de subconjunto em combinação com método plot(). Portanto, o método plot() funciona em ambos Series e DataFrame.

  • Eu quero comparar visualmente os valores NO2 medidos em Londres versus Paris.

    In [7]: air_quality.plot.scatter(x="station_london", y="station_paris", alpha=0.5)
    Out[7]: <AxesSubplot:xlabel='station_london', ylabel='station_paris'>
    

Além do gráfico de linha padrão ao usar a função plot(), várias alternativas estão disponíveis para mostrar os dados. Vamos usar algum Python padrão para obter uma visão geral dos métodos de plot disponíveis:

In [8]: [
   ...:     method_name
   ...:     for method_name in dir(air_quality.plot)
   ...:     if not method_name.startswith("_")
   ...: ]
   ...: 
Out[8]: 
['area',
 'bar',
 'barh',
 'box',
 'density',
 'hexbin',
 'hist',
 'kde',
 'line',
 'pie',
 'scatter']

Observação

Em muitos ambientes de desenvolvimento, bem como IPython e Jupyter Notebook, use o botão TAB para obter uma visão geral dos métodos disponíveis, por exemplo air_quality.plot.+ TAB.

Uma das opções é DataFrame.plot.box(), que se refere a um boxplot. O método box é aplicável nos dados de exemplo de qualidade do ar:

In [9]: air_quality.plot.box()
Out[9]: <AxesSubplot:>
  • Eu quero cada uma das colunas em uma subtrama separada.

    In [10]: axs = air_quality.plot.area(figsize=(12, 4), subplots=True)
    

    Subplots separados para cada uma das colunas de dados são suportados pelo argumento subplots das funções plot. As opções integradas disponíveis em cada uma das funções do pandas representam que vale a pena dar uma olhada.

  • Eu quero personalizar ainda mais, estender ou salvar o enredo resultante.

    In [11]: fig, axs = plt.subplots(figsize=(12, 4))
    
    In [12]: air_quality.plot.area(ax=axs)
    Out[12]: <AxesSubplot:xlabel='datetime'>
    
    In [13]: axs.set_ylabel("NO$_2$ concentration")
    Out[13]: Text(0, 0.5, 'NO$_2$ concentration')
    
    In [14]: fig.savefig("no2_concentrations.png")
    

Cada um dos objetos de trama criados por pandas é um objeto matplotlib. Como o Matplotlib oferece muitas opções para personalizar os gráficos, tornar explícito o link entre o pandas e o Matplotlib permite todo o poder do matplotlib no gráfico. Essa estratégia é aplicada no exemplo anterior:

fig, axs = plt.subplots(figsize=(12, 4))        # Create an empty matplotlib Figure and Axes
air_quality.plot.area(ax=axs)                   # Use pandas to put the area plot on the prepared Figure/Axes
axs.set_ylabel("NO$_2$ concentration")          # Do any matplotlib customization you like
fig.savefig("no2_concentrations.png")           # Save the Figure/Axes using the existing matplotlib method.

LEMBRAR

  • Os métodos .plot.* são aplicáveis em Series e DataFrames

  • Por padrão, cada uma das colunas é plotada como um elemento diferente (linha, boxplot, ...)

  • Qualquer plotagem criada por pandas é um objeto Matplotlib.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

domingo, 4 de abril de 2021

Como seleciono um subconjunto de um DataFrame?

Este tutorial usa o conjunto de dados do Titanic, armazenado como CSV. Os dados consistem nas seguintes colunas de dados:

  • PassengerId: Id de cada passageiro.
  • Survived: Este recurso tem valores 0 e 1. 0 para não sobreviveu e 1 para sobreviveu.
  • Pclass: Existem 3 classes: Classe 1, Classe 2 e Classe 3.
  • Name: Nome do passageiro.
  • Sex: gênero do passageiro.
  • Age: Idade do passageiro.
  • SibSp: Indicação de que o passageiro tem irmãos e cônjuge.
  • Parch: se o passageiro está sozinho ou tem família.
  • Ticket: Número do bilhete do passageiro.
  • Fare: Indicando a tarifa.
  • Cabin: A cabine do passageiro.
  • Embarked: a categoria embarcada.

O arquivo csv pode ser baixado aqui.

Como seleciono colunas específicas de um DataFrame?

  • Estou interessado na idade dos passageiros do Titanic.

    In [4]: ages = titanic["Age"]
    
    In [5]: ages.head()
    Out[5]: 
    0    22.0
    1    38.0
    2    26.0
    3    35.0
    4    35.0
    Name: Age, dtype: float64
    

    Para selecionar uma única coluna, use colchetes [] com o nome da coluna de interesse.

Cada coluna em um DataFrame é uma Series. Como uma única coluna é selecionada, o objeto retornado é uma Series pandas. Podemos verificar isso verificando o tipo de saída:

In [6]: type(titanic["Age"])
Out[6]: pandas.core.series.Series

E dê uma olhada no shape (forma) da saída:

In [7]: titanic["Age"].shape
Out[7]: (891,)

DataFrame.shape é um atributo (lembre-se do tutorial de leitura e escrita, não use parênteses para atributos) de uma Series pandas e que DataFrame contém o número de linhas e colunas: (nrows, ncolumns). Uma série de pandas é unidimensional e apenas o número de linhas é retornado.

  • Estou interessado na idade e no sexo dos passageiros do Titanic.

    In [8]: age_sex = titanic[["Age", "Sex"]]
    
    In [9]: age_sex.head()
    Out[9]: 
        Age     Sex
    0  22.0    male
    1  38.0  female
    2  26.0  female
    3  35.0  female
    4  35.0    male
    

    Para selecionar várias colunas, use uma lista de nomes de colunas dentro dos colchetes de seleção [].

Observação

Os colchetes internos definem uma lista Python com nomes de colunas, enquanto os colchetes externos são usados para selecionar os dados de um DataFrame pandas, como visto no exemplo anterior.

O tipo de dados retornado é um DataFrame pandas:

In [10]: type(titanic[["Age", "Sex"]])
Out[10]: pandas.core.frame.DataFrame
In [11]: titanic[["Age", "Sex"]].shape
Out[11]: (891, 2)

A seleção retornou um DataFrame com 891 linhas e 2 colunas. Lembre-se de que o DataFrame é bidimensional com dimensão de linha e coluna.

Como posso filtrar linhas específicas de um DataFrame?

  • Estou interessado nos passageiros com mais de 35 anos.

    In [12]: above_35 = titanic[titanic["Age"] > 35]
    
    In [13]: above_35.head()
    Out[13]: 
        PassengerId  Survived  Pclass                                               Name  ...    Ticket     Fare  Cabin  Embarked
    1             2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  ...  PC 17599  71.2833    C85         C
    6             7         0       1                            McCarthy, Mr. Timothy J  ...     17463  51.8625    E46         S
    11           12         1       1                           Bonnell, Miss. Elizabeth  ...    113783  26.5500   C103         S
    13           14         0       3                        Andersson, Mr. Anders Johan  ...    347082  31.2750    NaN         S
    15           16         1       2                   Hewlett, Mrs. (Mary D Kingcome)   ...    248706  16.0000    NaN         S
    
    [5 rows x 12 columns]
    

    Para selecionar linhas com base em uma expressão condicional, use uma condição dentro dos colchetes de seleção [].

A condição dentro dos colchetes de seleção titanic["Age"] > 35 verifica para quais linhas a coluna Age tem um valor maior que 35:

In [14]: titanic["Age"] > 35
Out[14]: 
0      False
1       True
2      False
3      False
4      False
       ...  
886    False
887    False
888    False
889    False
890    False
Name: Age, Length: 891, dtype: bool

A saída da expressão condicional ( >, mas também ==, !=, <, <=, ... iria funcionar) é realmente uma Series pandas de valores booleanos (quer True ou False) com o mesmo número de linhas que o DataFrame original. Essa Series de valores booleanos podem ser usados para filtrar o DataFrame colocando-o entre os colchetes de seleção []. Apenas as linhas para as quais o valor é True serão selecionadas.

Já sabemos que o DataFrame original Titanic consiste em 891 linhas. Vamos dar uma olhada no número de linhas que satisfazem a condição, verificando o atributo shape do DataFrame above_35:

In [15]: above_35.shape
Out[15]: (217, 12)
  • Estou interessado nos passageiros do Titanic da classe 2 e 3 da cabine.

    In [16]: class_23 = titanic[titanic["Pclass"].isin([2, 3])]
    
    In [17]: class_23.head()
    Out[17]: 
       PassengerId  Survived  Pclass                            Name     Sex  ...  Parch            Ticket     Fare Cabin  Embarked
    0            1         0       3         Braund, Mr. Owen Harris    male  ...      0         A/5 21171   7.2500   NaN         S
    2            3         1       3          Heikkinen, Miss. Laina  female  ...      0  STON/O2. 3101282   7.9250   NaN         S
    4            5         0       3        Allen, Mr. William Henry    male  ...      0            373450   8.0500   NaN         S
    5            6         0       3                Moran, Mr. James    male  ...      0            330877   8.4583   NaN         Q
    7            8         0       3  Palsson, Master. Gosta Leonard    male  ...      1            349909  21.0750   NaN         S
    
    [5 rows x 12 columns]
    

    Semelhante à expressão condicional, a função condicional isin() retorna True para cada linha em que os valores estão na lista fornecida. Para filtrar as linhas com base em tal função, use a função condicional dentro dos colchetes de seleção []. Nesse caso, a condição dentro dos colchetes de seleção titanic["Pclass"].isin([2, 3]) verifica quais linhas da coluna Pclass é 2 ou 3.

O acima é equivalente a filtrar por linhas para as quais a classe é 2 ou 3 e combinar as duas instruções com um operador (ou) |:

In [18]: class_23 = titanic[(titanic["Pclass"] == 2) | (titanic["Pclass"] == 3)]

In [19]: class_23.head()
Out[19]: 
   PassengerId  Survived  Pclass                            Name     Sex  ...  Parch            Ticket     Fare Cabin  Embarked
0            1         0       3         Braund, Mr. Owen Harris    male  ...      0         A/5 21171   7.2500   NaN         S
2            3         1       3          Heikkinen, Miss. Laina  female  ...      0  STON/O2. 3101282   7.9250   NaN         S
4            5         0       3        Allen, Mr. William Henry    male  ...      0            373450   8.0500   NaN         S
5            6         0       3                Moran, Mr. James    male  ...      0            330877   8.4583   NaN         Q
7            8         0       3  Palsson, Master. Gosta Leonard    male  ...      1            349909  21.0750   NaN         S

[5 rows x 12 columns]

Observação

Ao combinar várias instruções condicionais, cada condição deve estar entre parênteses (). Além disso, você não pode usar or / and você precisa usar o operador or (|) e o operador and (&).

  • Quero trabalhar com dados de passageiros cuja idade é conhecida.

    In [20]: age_no_na = titanic[titanic["Age"].notna()]
    
    In [21]: age_no_na.head()
    Out[21]: 
       PassengerId  Survived  Pclass                                               Name  ...            Ticket     Fare  Cabin  Embarked
    0            1         0       3                            Braund, Mr. Owen Harris  ...         A/5 21171   7.2500    NaN         S
    1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  ...          PC 17599  71.2833    C85         C
    2            3         1       3                             Heikkinen, Miss. Laina  ...  STON/O2. 3101282   7.9250    NaN         S
    3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  ...            113803  53.1000   C123         S
    4            5         0       3                           Allen, Mr. William Henry  ...            373450   8.0500    NaN         S
    
    [5 rows x 12 columns]
    

    A função condicional notna() retorna True para cada linha, os valores não são um Nullvalor. Como tal, isso pode ser combinado com os colchetes de seleção []para filtrar a tabela de dados.

Você pode se perguntar o que realmente mudou, já que as 5 primeiras linhas ainda são os mesmos valores. Uma maneira de verificar é verificar se a forma mudou:

In [22]: age_no_na.shape
Out[22]: (714, 12)

Como seleciono linhas e colunas específicas de um DataFrame?

  • Estou interessado nos nomes dos passageiros com mais de 35 anos.

    In [23]: adult_names = titanic.loc[titanic["Age"] > 35, "Name"]
    
    In [24]: adult_names.head()
    Out[24]: 
    1     Cumings, Mrs. John Bradley (Florence Briggs Th...
    6                               McCarthy, Mr. Timothy J
    11                             Bonnell, Miss. Elizabeth
    13                          Andersson, Mr. Anders Johan
    15                     Hewlett, Mrs. (Mary D Kingcome) 
    Name: Name, dtype: object
    

    Neste caso, um subconjunto de linhas e colunas é feito de uma vez e apenas usar colchetes de seleção [] não é mais suficiente. Os operadores loc / iloc são necessários na frente dos colchetes de seleção []. Ao usar loc / iloc, a parte antes da vírgula são as linhas que você deseja e a parte depois da vírgula são as colunas que você deseja selecionar.

Ao usar os nomes das colunas, rótulos de linha ou uma expressão de condição, use o operador loc na frente dos colchetes de seleção []. Para a parte antes e depois da vírgula, você pode usar um único rótulo, uma lista de rótulos, uma fatia de rótulos, uma expressão condicional ou dois pontos. Usar dois pontos especifica que você deseja selecionar todas as linhas ou colunas.

  • Estou interessado nas linhas 10 a 25 e nas colunas 3 a 5.

    In [25]: titanic.iloc[9:25, 2:5]
    Out[25]: 
        Pclass                                 Name     Sex
    9        2  Nasser, Mrs. Nicholas (Adele Achem)  female
    10       3      Sandstrom, Miss. Marguerite Rut  female
    11       1             Bonnell, Miss. Elizabeth  female
    12       3       Saundercock, Mr. William Henry    male
    13       3          Andersson, Mr. Anders Johan    male
    ..     ...                                  ...     ...
    20       2                 Fynney, Mr. Joseph J    male
    21       2                Beesley, Mr. Lawrence    male
    22       3          McGowan, Miss. Anna "Annie"  female
    23       1         Sloper, Mr. William Thompson    male
    24       3        Palsson, Miss. Torborg Danira  female
    
    [16 rows x 3 columns]
    

    Novamente, um subconjunto de linhas e colunas é feito de uma vez e apenas usar colchetes de seleção [] não é mais suficiente. Quando estiver especificamente interessado em certas linhas e / ou colunas com base em sua posição na tabela, use o operador iloc na frente dos colchetes de seleção [].

Ao selecionar linhas e / ou colunas específicas com loc ou iloc, novos valores podem ser atribuídos aos dados selecionados. Por exemplo, para atribuir o nome anonymous aos 3 primeiros elementos da terceira coluna:

In [26]: titanic.iloc[0:3, 3] = "anonymous"

In [27]: titanic.head()
Out[27]: 
   PassengerId  Survived  Pclass                                          Name  ...            Ticket     Fare  Cabin  Embarked
0            1         0       3                                     anonymous  ...         A/5 21171   7.2500    NaN         S
1            2         1       1                                     anonymous  ...          PC 17599  71.2833    C85         C
2            3         1       3                                     anonymous  ...  STON/O2. 3101282   7.9250    NaN         S
3            4         1       1  Futrelle, Mrs. Jacques Heath (Lily May Peel)  ...            113803  53.1000   C123         S
4            5         0       3                      Allen, Mr. William Henry  ...            373450   8.0500    NaN         S

[5 rows x 12 columns]

LEMBRAR

  • Ao selecionar subconjuntos de dados, colchetes [] são usados.

  • Dentro desses colchetes, você pode usar um único rótulo de coluna / linha, uma lista de rótulos de coluna / linha, uma fatia de rótulos, uma expressão condicional ou dois pontos.

  • Selecione linhas e / ou colunas específicas usando loc ao usar os nomes de linha e coluna.

  • Selecione linhas e / ou colunas específicas usando iloc ao usar as posições na tabela.

  • Você pode atribuir novos valores a uma seleção com base em loc / iloc.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Enums e correspondência de padrões em Rust

Neste capítulo, examinaremos as enumerações, também conhecidas como enums. Enums permitem definir um tipo enumerando suas variantes possíveis. Primeiro, vamos definir e usar um enum para mostrar como um enum pode codificar o significado junto com os dados. A seguir, exploraremos um enum particularmente útil, chamado Option, que expressa que um valor pode ser algo ou nada. Em seguida, veremos como a correspondência de padrões na expressão match facilita a execução de diferentes códigos para diferentes valores de um enum. Finalmente, vamos cobrir como a construção if let é outro conveniencia e disponível para você lidar com enums em seu código.

Enums são um recurso em muitas linguagens, mas seus recursos diferem em cada linguagem. Os enums de Rust são mais semelhantes aos tipos de dados algébricos em linguagens funcionais, como F#, OCaml e Haskell.

Definindo um Enum

Vamos examinar uma situação que podemos querer expressar em código e ver por que enums são úteis e mais apropriados do que structs neste caso. Digamos que precisamos trabalhar com endereços IP. Atualmente, dois padrões principais são usados para endereços IP: versão quatro e versão seis. Estas são as únicas possibilidades para um endereço IP que nosso programa encontrará: podemos enumerar todas as variantes possíveis, que é de onde a enumeração obtém seu nome.

Qualquer endereço IP pode ser um endereço de versão quatro ou seis, mas não os dois ao mesmo tempo. Essa propriedade dos endereços IP torna a estrutura de dados enum apropriada, porque os valores enum podem ser apenas uma de suas variantes. Os endereços da versão quatro e da versão seis ainda são fundamentalmente endereços IP, portanto, devem ser tratados como o mesmo tipo quando o código está lidando com situações que se aplicam a qualquer tipo de endereço IP.

Podemos expressar esse conceito em código, definindo uma enumeração IpAddrKind e listando os tipos possíveis que um endereço IP pode ser, V4 ou V6. Estas são as variantes do enum:

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

IpAddrKind agora é um tipo de dados personalizado que podemos usar em outro lugar em nosso código.

Valores Enum

Podemos criar instâncias de cada uma das duas variantes IpAddrKind assim:

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

Observe que as variantes do enum têm namespaces sob seu identificador e usamos dois-pontos duplos para separar os dois. A razão pela qual isso é útil é que agora ambos os valores IpAddrKind::V4 e IpAddrKind::V6 são do mesmo tipo: IpAddrKind. Podemos então, por exemplo, definir uma função que leva qualquer IpAddrKind:

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

E podemos chamar essa função com qualquer uma das variantes:

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

Usar enums tem ainda mais vantagens. Pensando mais no nosso tipo de endereço IP, no momento não temos como armazenar os dados reais do endereço IP; só sabemos de que tipo é. Dado que você acabou de aprender sobre structs no Capítulo 5, você pode resolver esse problema conforme mostrado na Listagem 6-1.

fn main() {
    enum IpAddrKind {
        V4,
        V6,
    }

    struct IpAddr {
        kind: IpAddrKind,
        address: String,
    }

    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };
}

Listagem 6-1: Armazenando os dados e a variante IpAddrKind de um endereço IP usando uma struct.

Aqui, definimos uma estrutura IpAddr que possui dois campos: um campo kind do tipo IpAddrKind (o enum que definimos anteriormente) e um campo address do tipo String. Temos duas instâncias desta estrutura. O primeiro, home, tem um valor IpAddrKind::V4 como kind com dados de endereço associado 127.0.0.1. A segunda instância, loopback, tem a outra variante de IpAddrKind como seu valor de kind sendo V6, e tem o endereço ::1 associado a ela. Nós usamos uma estrutura para agrupar os valores kind e address juntos, então agora a variante está associada com o valor.

Podemos representar o mesmo conceito de uma maneira mais concisa usando apenas um enum, em vez de um enum dentro de uma estrutura, colocando dados diretamente em cada variante de enum. Esta nova definição do enum IpAddr diz que ambas variantes, V4 e V6, terão valores String associados:

fn main() {
    enum IpAddr {
        V4(String),
        V6(String),
    }

    let home = IpAddr::V4(String::from("127.0.0.1"));

    let loopback = IpAddr::V6(String::from("::1"));
}

Anexamos dados a cada variante do enum diretamente, portanto, não há necessidade de uma estrutura extra.

Há outra vantagem em usar um enum em vez de uma estrutura: cada variante pode ter diferentes tipos e quantidades de dados associados. Os endereços IP do tipo da versão quatro sempre terão quatro componentes numéricos que terão valores entre 0 e 255. Se quiséssemos armazenar endereços V4 como quatro valores u8, mas ainda expressar endereços V6 como um valor String, não poderíamos com uma estrutura. Enums lidam com este caso com facilidade:

fn main() {
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);

    let loopback = IpAddr::V6(String::from("::1"));
}

Mostramos várias maneiras diferentes de definir estruturas de dados para armazenar endereços IP da versão quatro e da versão seis. No entanto, ao que parece, querer armazenar endereços IP e codificar que tipo eles são é tão comum que a biblioteca padrão tem uma definição que podemos usar! Vejamos como a biblioteca padrão define IpAddr: ela tem o enum exato e as variantes que definimos e usamos, mas incorpora os dados de endereço dentro das variantes na forma de duas estruturas diferentes, que são definidas de forma diferente para cada variante:


#![allow(unused)]
fn main() {
struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}
}

Este código ilustra que você pode colocar qualquer tipo de dado dentro de uma variante enum: strings, tipos numéricos ou structs, por exemplo. Você pode até incluir outro enum! Além disso, os tipos de biblioteca padrão geralmente não são muito mais complicados do que o que você pode imaginar.

Observe que, embora a biblioteca padrão contenha uma definição para IpAddr, ainda podemos criar e usar nossa própria definição sem conflito porque não trouxemos a definição da biblioteca padrão para o nosso escopo. Falaremos mais sobre como trazer os tipos no escopo no Capítulo 7.

Vejamos outro exemplo de enum na Listagem 6-2: este possui uma grande variedade de tipos embutidos em suas variantes.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {}

Listagem 6-2: um Messageenum cujas variantes cada uma armazena diferentes quantidades e tipos de valores.

Este enum tem quatro variantes com tipos diferentes:

  • Quit não tem nenhum dado associado a ele.
  • Move inclui uma estrutura anônima dentro dela.
  • Write inclui uma única String.
  • ChangeColor inclui três valores i32.

Definir um enum com variantes como as da Listagem 6-2 é semelhante a definir diferentes tipos de definições de estrutura, exceto que o enum não usa a structpalavra - chave e todas as variantes são agrupadas sob o Message tipo. Os seguintes structs podem conter os mesmos dados que as variantes enum anteriores contêm:

struct QuitMessage; // unit struct
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

fn main() {}

Mas se usássemos as diferentes estruturas, cada uma com seu próprio tipo, não poderíamos definir tão facilmente uma função para receber qualquer um desses tipos de mensagens como poderíamos com o Messageenum definido na Listagem 6-2, que é um tipo único.

Há mais uma semelhança entre enums e structs: assim como podemos definir métodos em structs usando impl, também podemos definir métodos em enums. Aqui está um método chamado call que podemos definir em nosso enum Message:

fn main() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }

    impl Message {
        fn call(&self) {
            // method body would be defined here
        }
    }

    let m = Message::Write(String::from("hello"));
    m.call();
}

O corpo do método usaria self para obter o valor com o qual chamamos o método. Neste exemplo, criamos uma variável m que tem o valor Message::Write(String::from("hello")) e é isso que self será no corpo do método call quando m.call() for executado.

Vamos olhar um outro enum na biblioteca padrão que é muito comum e útil: Option.

O Enum Option e suas vantagens sobre os valores nulos

Na seção anterior, vimos como o enum IpAddr nos permite usar o sistema de tipos de Rust para codificar mais informações do que apenas os dados em nosso programa. Esta seção explora um estudo de caso de Option, que é outro enum definido pela biblioteca padrão. O tipo Option é usado em muitos lugares porque codifica o cenário muito comum em que um valor pode ser algo ou nada. Expressar esse conceito em termos do sistema de tipos significa que o compilador pode verificar se você tratou de todos os casos que deveria tratar; essa funcionalidade pode evitar bugs que são extremamente comuns em outras linguagens de programação.

O design da linguagem de programação geralmente é pensado em termos de quais recursos você inclui, mas os recursos que você exclui também são importantes. Rust não possui o recurso nulo que muitas outras linguagens possuem. Nulo é um valor que significa que não há valor lá. Em linguagens com nulo, as variáveis ​​podem sempre estar em um de dois estados: nulo ou não nulo.

Em sua apresentação de 2009, "Referências nulas: o erro de um bilhão de dólares", Tony Hoare, o inventor do nulo, disse:

Eu chamo isso de meu erro de um bilhão de dólares. Naquela época, eu estava projetando o primeiro sistema de tipos abrangente para referências em uma linguagem orientada a objetos. Meu objetivo era garantir que todo uso de referências fosse absolutamente seguro, com checagem realizada automaticamente pelo compilador. Mas não pude resistir à tentação de colocar uma referência nula, simplesmente porque era muito fácil de implementar. Isso levou a inúmeros erros, vulnerabilidades e falhas no sistema, que provavelmente causaram um bilhão de dólares em dores e danos nos últimos quarenta anos.

O problema com valores nulos é que se você tentar usar um valor nulo como um valor não nulo, obterá algum tipo de erro. Como essa propriedade nula ou não nula é difundida, é extremamente fácil cometer esse tipo de erro.

No entanto, o conceito que nulo está tentando expressar ainda é útil: um nulo é um valor que atualmente é inválido ou ausente por algum motivo.

O problema não é realmente com o conceito, mas com a implementação particular. Como tal, Rust não tem nulos, mas tem um enum que pode codificar o conceito de um valor presente ou ausente. Este enum é Option<T> e é definido pela biblioteca padrão da seguinte forma:


#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

O enum Option<T> é tão útil que até está incluído no prelúdio; você não precisa colocá-lo no escopo explicitamente. Além disso, são suas variantes: você pode usar Some e None diretamente sem o prefixo Option::. O enum Option<T> ainda é apenas um enum regular Some(T) e None ainda são variantes do tipo Option<T>.

A sintaxe <T> é um recurso do Rust sobre o qual ainda não falamos. É um parâmetro de tipo genérico e abordaremos os genéricos com mais detalhes no Capítulo 10. Por enquanto, tudo o que você precisa saber é que <T> a variante Some do enum Option pode conter um dado de qualquer tipo. Aqui estão alguns exemplos de uso dos valores Option para conter tipos de número e tipos de string:

fn main() {
    let some_number = Some(5);
    let some_string = Some("a string");

    let absent_number: Option<i32> = None;
}

Se usarmos None em vez de Some, precisamos dizer a Rust que tipo Option<T> temos, porque o compilador não pode inferir o tipo que a variante Some manterá olhando apenas para um valor None.

Quando temos um valor Some, sabemos que ele está presente e o valor está dentro de Some. Quando temos um valor None, em certo sentido, significa a mesma coisa que nulo: não temos um valor válido. Então, por que ter algum Option<T> é melhor do que ter null?

Resumindo, como Option<T> e T(onde T pode ser qualquer tipo) são tipos diferentes, o compilador não nos permite usar um valor Option<T> como se fosse definitivamente um valor válido. Por exemplo, este código não compila porque está tentando adicionar um valor i8 a um Option<i8>:

Esse código não compila Esse código não compila.

fn main() {
    let x: i8 = 5;
    let y: Option<i8> = Some(5);

    let sum = x + y;
}

Se executarmos esse código, receberemos uma mensagem de erro como esta:

$ cargo run
   Compiling enums v0.1.0 (file:///projects/enums)
error[E0277]: cannot add `Option<i8>` to `i8`
 --> src/main.rs:5:17
  |
5 |     let sum = x + y;
  |                 ^ no implementation for `i8 + Option<i8>`
  |
  = help: the trait `Add<Option<i8>>` is not implemented for `i8`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `enums`

To learn more, run the command again with --verbose.

Intenso! Na verdade, essa mensagem de erro significa que Rust não entende como adicionar um i8 e um Option<i8>, porque são tipos diferentes. Quando temos um valor de um tipo como i8 em Rust, o compilador garantirá que sempre tenhamos um valor válido. Podemos prosseguir com segurança sem precisar verificar se há nulo antes de usar esse valor. Somente quando temos um Option<i8> (ou qualquer tipo de valor com o qual estamos trabalhando) é que temos que nos preocupar com a possibilidade de não ter um valor, e o compilador se certificará de que trataremos desse caso antes de usar o valor.

Em outras palavras, você deve converter um Option<T> em a T antes de poder realizar operações T com ele. Geralmente, isso ajuda a detectar um dos problemas mais comuns com nulo: assumir que algo não é nulo quando na verdade é.

Não ter que se preocupar em assumir incorretamente um valor não nulo ajuda você a ter mais confiança em seu código. Para ter um valor que possivelmente pode ser nulo, você deve ativá-lo explicitamente definindo o tipo desse valor Option<T>. Então, ao usar esse valor, você deve lidar explicitamente com o caso em que o valor é nulo. Onde quer que um valor tenha um tipo que não seja um Option<T>, você pode assumir com segurança que o valor não é nulo. Esta foi uma decisão de design deliberada do Rust para limitar a difusão do null e aumentar a segurança do código do Rust.

Então, como você obtém o valor T de uma variante Some quando você tem um valor do tipo Option<T> para que possa usar esse valor? O enum Option<T> possui um grande número de métodos que são úteis em uma variedade de situações; você pode verificá-los em sua documentação. Familiarizar-se com os métodos do Option<T> será extremamente útil em sua jornada com o Rust.

Em geral, para usar um valor Option<T>, você deseja ter um código que trate de cada variante. Você quer algum código que será executado apenas quando você tiver um valor Some(T), e esse código tem permissão para usar o interno T. Você deseja que algum outro código seja executado se tiver um valor None e esse código não tiver um valor T disponível. A expressão match é uma construção de fluxo de controle que faz exatamente isso quando usada com enums: ela executará um código diferente dependendo de qual variante da enum ela possui, e esse código pode usar os dados dentro do valor correspondente.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

sábado, 3 de abril de 2021

Como leio e escrevo dados tabulares com pandas?

Este tutorial usa o conjunto de dados do Titanic, armazenado como CSV. Os dados consistem nas seguintes colunas de dados:

  • PassengerId: Id de cada passageiro.
  • Survived: Este recurso tem valores 0 e 1. 0 para não sobreviveu e 1 para sobreviveu.
  • Pclass: Existem 3 classes: Classe 1, Classe 2 e Classe 3.
  • Name: Nome do passageiro.
  • Sex: gênero do passageiro.
  • Age: Idade do passageiro.
  • SibSp: Indicação de que o passageiro tem irmãos e cônjuge.
  • Parch: se o passageiro está sozinho ou tem família.
  • Ticket: Número do bilhete do passageiro.
  • Fare: Indicando a tarifa.
  • Cabin: A cabine do passageiro.
  • Embarked: a categoria embarcada.

O arquivo csv pode ser baixado aqui.

  • Quero analisar os dados dos passageiros do Titanic, disponíveis como um arquivo CSV.

    In [2]: titanic = pd.read_csv("data/titanic.csv")
    

    O pandas fornece a função read_csv() que ler dados armazenados num arquivo csv e com os dados desse arquivo cria um DataFrame pandas. O pandas suporta muitos formatos de arquivo ou fontes de dados diferentes fora da caixa (csv, excel, sql, json, parquet, ...), cada um deles com o prefixo read_*.

Certifique-se de sempre verificar os dados após lê-los. Ao exibir um DataFrame, a primeira e as últimas 5 linhas serão mostradas por padrão:

In [3]: titanic
Out[3]: 
     PassengerId  Survived  Pclass  ...     Fare Cabin  Embarked
0              1         0       3  ...   7.2500   NaN         S
1              2         1       1  ...  71.2833   C85         C
2              3         1       3  ...   7.9250   NaN         S
3              4         1       1  ...  53.1000  C123         S
4              5         0       3  ...   8.0500   NaN         S
..           ...       ...     ...  ...      ...   ...       ...
886          887         0       2  ...  13.0000   NaN         S
887          888         1       1  ...  30.0000   B42         S
888          889         0       3  ...  23.4500   NaN         S
889          890         1       1  ...  30.0000  C148         C
890          891         0       3  ...   7.7500   NaN         Q

[891 rows x 12 columns]
  • Quero ver as primeiras 8 linhas de um DataFrame do pandas.

    In [4]: titanic.head(8)
    Out[4]: 
       PassengerId  Survived  Pclass                                               Name  ...            Ticket     Fare  Cabin  Embarked
    0            1         0       3                            Braund, Mr. Owen Harris  ...         A/5 21171   7.2500    NaN         S
    1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  ...          PC 17599  71.2833    C85         C
    2            3         1       3                             Heikkinen, Miss. Laina  ...  STON/O2. 3101282   7.9250    NaN         S
    3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  ...            113803  53.1000   C123         S
    4            5         0       3                           Allen, Mr. William Henry  ...            373450   8.0500    NaN         S
    5            6         0       3                                   Moran, Mr. James  ...            330877   8.4583    NaN         Q
    6            7         0       1                            McCarthy, Mr. Timothy J  ...             17463  51.8625    E46         S
    7            8         0       3                     Palsson, Master. Gosta Leonard  ...            349909  21.0750    NaN         S
    
    [8 rows x 12 columns]
    

    Para ver as primeiras N linhas de um DataFrame, use o método head() com o número necessário de linhas (neste caso 8) como argumento.

Observação

Em vez disso, está interessado nas últimas N linhas? pandas também fornece o método tail(). Por exemplo, titanic.tail(10) retornará as últimas 10 linhas do DataFrame.

Uma verificação de como o pandas interpreta cada um dos tipos de dados da coluna pode ser feita solicitando o atributo dtypes do pandas:

In [5]: titanic.dtypes
Out[5]: 
PassengerId      int64
Survived         int64
Pclass           int64
Name            object
Sex             object
Age            float64
SibSp            int64
Parch            int64
Ticket          object
Fare           float64
Cabin           object
Embarked        object
dtype: object

Para cada uma das colunas, o tipo de dados usado é listado. Os tipos de dados DataFrame são integers (int64), floats (float64) e strings (object).

Observação

Ao solicitar o atributo dtypes, não são usados ​​colchetes! dtypes é um atributo do DataFrame e das Series. Os atributos do DataFrame ou Series não precisam de colchetes. Os atributos representam uma característica de um DataFrame / Series, enquanto um método (que requer colchetes) faz algo com o DataFrame / Series conforme apresentado no primeiro tutorial.

  • Meu colega solicitou os dados do Titanic em uma planilha.

    In [6]: titanic.to_excel("titanic.xlsx", sheet_name="passengers", index=False)
    

    Enquanto as funções com o prefixo read_* são usadas para ler dados e criar um DataFrame ou Series pandas, os métodos com o prefixo to_* são usados ​​para armazenar um DataFrame ou Series num arquivo (por exemplo, CSV ou xls). O método to_excel() armazena os dados como um arquivo Excel. No exemplo aqui, o sheet_name é nomeado passengers em vez do padrão Sheet1 . Ao definir o índice de linha como index=False, os rótulos não são salvos na planilha.

A função de leitura equivalente read_excel() recarregará os dados em um DataFrame:

In [7]: titanic = pd.read_excel("titanic.xlsx", sheet_name="passengers")
In [8]: titanic.head()
Out[8]: 
   PassengerId  Survived  Pclass                                               Name  ...            Ticket     Fare  Cabin  Embarked
0            1         0       3                            Braund, Mr. Owen Harris  ...         A/5 21171   7.2500    NaN         S
1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  ...          PC 17599  71.2833    C85         C
2            3         1       3                             Heikkinen, Miss. Laina  ...  STON/O2. 3101282   7.9250    NaN         S
3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  ...            113803  53.1000   C123         S
4            5         0       3                           Allen, Mr. William Henry  ...            373450   8.0500    NaN         S

[5 rows x 12 columns]
  • Estou interessado em um resumo técnico de um DataFrame.

    In [9]: titanic.info()
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 891 entries, 0 to 890
    Data columns (total 12 columns):
     #   Column       Non-Null Count  Dtype  
    ---  ------       --------------  -----  
     0   PassengerId  891 non-null    int64  
     1   Survived     891 non-null    int64  
     2   Pclass       891 non-null    int64  
     3   Name         891 non-null    object 
     4   Sex          891 non-null    object 
     5   Age          714 non-null    float64
     6   SibSp        891 non-null    int64  
     7   Parch        891 non-null    int64  
     8   Ticket       891 non-null    object 
     9   Fare         891 non-null    float64
     10  Cabin        204 non-null    object 
     11  Embarked     889 non-null    object 
    dtypes: float64(2), int64(5), object(5)
    memory usage: 83.7+ KB
    

    O método info() fornece informações técnicas sobre o DataFrame, então vamos explicar a saída com mais detalhes:

    • É realmente um DataFrame.

    • Existem 891 entradas, ou seja, 891 linhas.

    • Cada linha possui um rótulo de linha (também conhecido como index) com valores que variam de 0 a 890.

    • A tabela possui 12 colunas. A maioria das colunas tem um valor para cada uma das linhas (todos os 891 valores têm non-null). Algumas colunas têm valores ausentes e menos de 891 non-nullvalores.

    • As colunas Name, Sex, Cabine Embarkedconsiste de dados textuais (cordas, aka object). As outras colunas são dados numéricos com alguns deles números inteiros (aka integer) e outros são números reais (aka float)

    • O tipo de dados (caracteres, inteiros, ...) nas diferentes colunas são resumidos listando o dtypes.

    • A quantidade aproximada de RAM usada para armazenar o DataFrame também é fornecida.

LEMBRAR

  • A obtenção de dados para o pandas de muitos formatos de arquivo ou fontes de dados diferentes é suportada por funções read_*.

  • A exportação de dados de pandas é fornecida por diferentes métodos com prefixo to_*.

  • Os métodos head / tail / info e o atributo dtypes são convenientes para uma primeira verificação.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Que tipo de dados podemos manipular com pandas?

  • Eu quero começar a usar pandas

    In [1]: import pandas as pd

    Para carregar o pacote pandas e começar a trabalhar com ele, importe o pacote. O apelido aceito pela comunidade para os pandas é pd, portanto, carregar o pandas com o apelido pd é uma prática padrão assumida para toda a documentação dos pandas.

representação da tabela de dados do pandas

  • Quero armazenar os dados dos passageiros do Titanic. Para vários passageiros, conheço os dados de nome (caracteres), idade (inteiros) e sexo (masculino / feminino).

    In [2]: df = pd.DataFrame(
       ...:     {
       ...:         "Name": [
       ...:             "Braund, Mr. Owen Harris",
       ...:             "Allen, Mr. William Henry",
       ...:             "Bonnell, Miss. Elizabeth",
       ...:         ],
       ...:         "Age": [22, 35, 58],
       ...:         "Sex": ["male", "male", "female"],
       ...:     }
       ...: )
       ...: 
    
    In [3]: df
    Out[3]: 
                           Name  Age     Sex
    0   Braund, Mr. Owen Harris   22    male
    1  Allen, Mr. William Henry   35    male
    2  Bonnell, Miss. Elizabeth   58  female

    Para armazenar dados manualmente em uma tabela, crie um DataFrame. Ao usar um dicionário de listas Python, as colchetes do dicionário serão usadas como cabeçalhos de coluna e os valores em cada lista como colunas do DataFrame.

Um DataFrame é uma estrutura de dados bidimensional que pode armazenar dados de diferentes tipos (incluindo caracteres, inteiros, valores de ponto flutuante, dados categóricos e mais) em colunas. É semelhante a uma planilha, uma tabela SQL ou o data.frame em R.

  • A tabela possui 3 colunas, cada uma delas com um rótulo de coluna. Os rótulos das colunas são Name, Age e Sex, respectivamente.

  • A coluna Name consiste em dados textuais com cada valor com uma string, a coluna Age são números e a coluna Sex são dados textuais.

No software de planilha, a representação da tabela de nossos dados seria muito semelhante:

Cada coluna no DataFrame é uma Series

  • Estou apenas interessado em trabalhar com os dados da coluna Age.

    In [4]: df["Age"]
    Out[4]: 
    0    22
    1    35
    2    58
    Name: Age, dtype: int64
    

    Ao selecionar uma única coluna do DataFrame pandas, o resultado são Series pandas. Para selecionar a coluna, use o rótulo da coluna entre colchetes [].

Observação

Se você estiver familiarizado com os dicionários Python, a seleção de uma única coluna é muito semelhante à seleção dos valores do dicionário com base no colchete.

Você também pode criar uma Series do zero:

In [5]: ages = pd.Series([22, 35, 58], name="Age")

In [6]: ages
Out[6]: 
0    22
1    35
2    58
Name: Age, dtype: int64

Uma Series pandas não tem rótulos de coluna, pois é apenas uma coluna de um DataFrame. A Series tem rótulos de linha.

Faça algo com um DataFrame ou Série

  • Eu quero saber a idade máxima dos passageiros.

    Podemos fazer isso no DataFrame selecionando a coluna Age e aplicando max():

    In [7]: df["Age"].max()
    Out[7]: 58
    

    Ou para a Series:

    In [8]: ages.max()
    Out[8]: 58
    

Conforme ilustrado pelo método max(), você pode fazer coisas com um DataFrame ou Series. O pandas oferece muitas funcionalidades, cada uma delas um método que você pode aplicar a um DataFrame ou Series. Como os métodos são funções, não se esqueça de usar parênteses ().

  • Estou interessado em algumas estatísticas básicas dos dados numéricos da minha tabela de dados.

    In [9]: df.describe()
    Out[9]: 
                 Age
    count   3.000000
    mean   38.333333
    std    18.230012
    min    22.000000
    25%    28.500000
    50%    35.000000
    75%    46.500000
    max    58.000000
    

    O método describe() fornece uma visão geral rápida dos dados numéricos em um DataFrame. Como as colunas Name e Sex são dados textuais, eles, por padrão, não são considerados pelo método describe().

Muitas operações do pandas retornam um DataFrame ou uma Series. O método describe() é um exemplo de uma operação de pandas retornando uma Series pandas.

Observação

Isto é só um ponto de partida. Semelhante ao software de planilha, o pandas representa os dados como uma tabela com colunas e linhas. Além da representação, também as manipulações de dados e cálculos que você faria em um software de planilha são suportados pelo pandas. Continue lendo os próximos tutoriais para começar!

LEMBRAR

  • Importe o pacote, também conhecido como import pandas as pd.

  • Uma tabela de dados é armazenada como um pandas DataFrame.

  • Cada coluna em um DataFrame é uma Series.

  • Você pode fazer coisas aplicando um método a um DataFrame ou Series.

Traduzido por Acervo Lima. O original pode ser acessado aqui.