<jupyter_start><jupyter_text>MAT281 -  Introducción a Pandas  Objetivos de la clase* Aprender conceptos básicos de la librería pandas.  Contenidos* [Pandas](c1)  Pandas[Pandas](https://pandas.pydata.org/) es un paquete de Python que proporciona estructuras de datos rápidas, flexibles y expresivas diseñadas para que trabajar con datos "relacionales" o "etiquetados" sea fácil e intuitivo. Su objetivo es ser el bloque de construcción fundamental de alto nivel para hacer análisis de datos prácticos del mundo real en Python. Además, tiene el objetivo más amplio de convertirse en la herramienta de análisis/manipulación de datos de código abierto más potente y flexible disponible en cualquier idioma. Ya está en camino hacia este objetivo.  Series y DataFrames* Las **series** son  arreglos unidimensionales con etiquetas. Se puede pensar como una generalización de los diccionarios de Python. * Los **dataframe** son arreglos bidimensionales y una extensión natural de las series. Se puede pensar como la generalización de un numpy.array.  1.- Pandas Series Operaciones Básicas con series<jupyter_code># importar libreria: pandas, os
import pandas as pd
import numpy as np
import os
# crear serie
my_serie = pd.Series(range(3, 33, 3), index=list('abcdefghij'))

# imprimir serie
print("serie:")
print( my_serie )
# tipo 
print("type:")
print( type(my_serie) )
# valores 
print("values:")
print(my_serie.values)
# indice
print("index:")
print(my_serie.index)
# acceder al valor de la serie: directo
print("direct:")
print(my_serie['b'])
# acceder al valor de la serie: loc
print("loc:")
print(my_serie.loc['b'])
# acceder al valor de la serie: iloc con indice
print("iloc:")
print(my_serie.iloc[1])
# editar valores
print("edit:")
print("\nold 'd':",my_serie.loc['d'] )
my_serie.loc['d'] = 1000
print("new 'd':",my_serie.loc['d'] )<jupyter_output>edit:

old 'd': 12
new 'd': 1000<jupyter_text>Manejo de FechasPandas también trae módulos para trabajar el formato de fechas.<jupyter_code># crear serie de fechas
date_rng = pd.date_range(start='1/1/2019', end='1/03/2019', freq='4H')

# imprimir serie
print("serie:")
print( date_rng )
# tipo 
print("type:\n")
print( type(date_rng) )
# elementos de datetime a string 
string_date_rng = [str(x) for x in date_rng]

print("datetime to string: \n")
print( np.array(string_date_rng) )
# elementos de string a datetime 
timestamp_date_rng = pd.to_datetime(string_date_rng, infer_datetime_format=True)

print("string to datetime:\n")
print( timestamp_date_rng )
# obtener fechas
print("date:\n")
print(timestamp_date_rng.date)
# obtener horas
print("hour:\n")
print(timestamp_date_rng.hour)<jupyter_output>hour:

Int64Index([0, 4, 8, 12, 16, 20, 0, 4, 8, 12, 16, 20, 0], dtype='int64')<jupyter_text>Operaciones matemáticasAl igual que numpy, las series de pandas pueden realizar operaciones matemáticas similares (mientrás los arreglos a operar sean del tipo numérico). Por otro lado existen otras funciones de utilidad.<jupyter_code># crear serie
s1 = pd.Series([1,1,1,2,2,2,3,3,3,4,5,5,5,5])


print(f"max:    {s1.max()}") # maximo
print(f"min:    {s1.min()}") # minimo
print(f"mean:   {s1.mean()}") # promedio
print(f"median: {s1.median()}") # mediana<jupyter_output>max:    5
min:    1
mean:   3.0
median: 3.0<jupyter_text>MaskingExisten módulos para acceder a valores que queremos que cumplan una determinada regla. Por ejemplo, acceder al valor máximo de una serie. En este caso a esta regla la denominaremos *mask*.<jupyter_code># 1. definir valor maximo 
n_max = s1.max()

# 2.- definir "mask" que busca el valor
mask = (s1 == n_max)

# 3.- aplicar mask sobre la serie
s1[mask]<jupyter_output><empty_output><jupyter_text>Valores Nulos o datos perdidosEn algunas ocaciones, los arreglos no tienen información en una determinada posición, lo cual puede ser perjudicial si no se tiene control sobre estos valores. a) Encontrar valores nulos<jupyter_code># crear serie
s_null = pd.Series([1,2,np.nan,4,5,6,7,np.nan,9])
s_null
# mask valores nulos
print("is null?:\n")
print(s_null.isnull() )
# filtrar valores nulos
print("null serie: \n")
print(s_null[s_null.isnull()] )<jupyter_output>null serie: 

2   NaN
7   NaN
dtype: float64<jupyter_text>b) Encontrar valores no nulos<jupyter_code># imprimir serie
print("serie:")
print( s_null )
# mask valores no nulos
print("\nis not null?:")
print(s_null.notnull() )
# filtrar valores no nulos
print("\nserie with not null values")
print(s_null[s_null.notnull()] )<jupyter_output>serie with not null values
0    1.0
1    2.0
3    4.0
4    5.0
5    6.0
6    7.0
8    9.0
dtype: float64<jupyter_text>La pregunta que nos queda hacer es: ¿ Qué se debe hacer con los valores nulos ?, la respuesta es **depende**.   * Si tenemos muchos datos, lo más probable es que se puedan eliminar estos datos sin culpa.   * Si se tienen poco datos, lo más probable es que se necesite inputar un valor por defecto a los valores nulos (**ejemplo**: el promedio).  2.- Pandas Dataframes Trabajando  con DataFramesComo se mencina anteriormente, los dataframes son arreglos de series, los cuales pueden ser de distintos tipos (numéricos, string, etc.). En esta parte mostraremos un ejemplo aplicado de las distintas funcionalidades de los dataframes.  Creación de dataframesLa creación se puede hacer de variadas formas con listas, dictionarios , numpy array , entre otros.<jupyter_code># empty dataframe
df_empty = pd.DataFrame()
df_empty
# dataframe with list
df_list = pd.DataFrame(
    [
        ["nombre_01", "apellido_01", 60],
        ["nombre_02", "apellido_02", 14]
    ], columns = ["nombre", "apellido", "edad"]
)
df_list
# dataframe with dct
df_dct =  pd.DataFrame(
    {
        "nombre": ["nombre_01", "nombre_02"],
        "apellido": ["apellido_01", "apellido_02"],
        "edad": np.array([60,14]),
    }
)
df_dct<jupyter_output><empty_output><jupyter_text>Lectura de datos con dataframesEn general, cuando se trabajan con datos, estos se almacenan en algún lugar y en algún tipo de formato, por ejemplo: * .txt * .csv * .xlsx * .db * etc.  Para cada formato, existe un módulo para realizar la lectura de datos. En este caso, se analiza el conjunto de datos 'player_data.csv', el cual muestra informacion básica de algunos jugadores de la NBA.<jupyter_code># load data ## crea la ruta
player_data = pd.read_csv(os.path.join('data', 'player_data.csv'), index_col='name')<jupyter_output><empty_output><jupyter_text>Módulos básicosExisten módulos para comprender rápidamente la naturaleza del dataframe.<jupyter_code># first 5 rows
print("first 5 rows:")
player_data.head(5)
# last 5 rows
print("\nlast 5 rows:")
player_data.tail(5)
# tipo
print("\ntype of dataframe:")
type(player_data)
# tipo por columns
print("\ntype of columns:")
player_data.dtypes
# dimension
print("\nshape:")
player_data.shape
# columna posicion
print("\ncolumn 'position': ")
player_data['position'].head()
player_data.columns<jupyter_output><empty_output><jupyter_text>Exploración de datosExisten módulos de pandas que realizan resumen de la información que dispone el dataframe.<jupyter_code># descripcion 
player_data.describe(include='all')<jupyter_output><empty_output><jupyter_text>Operando sobre DataframesCuando se trabaja con un conjunto de datos, se crea una dinámica de preguntas y respuestas, en donde a medida que necesito información, se va accediendo al dataframe. En algunas ocaciones es directo, basta un simple módulo, aunque en otras será necesaria realizar operaciones un poco más complejas. Por ejemplo, del conjunto de datos en estudio, se esta interesado en responder las siguientes preguntas:  a) Determine si el dataframe tiene valores nulos<jupyter_code>player_data.notnull().all(axis=1).head(10)<jupyter_output><empty_output><jupyter_text>b) Elimine los valores nulos del dataframe<jupyter_code>player_data = player_data[lambda df: df.notnull().all(axis=1)]
player_data.head()<jupyter_output><empty_output><jupyter_text>c) Determinar el tiempo de cada jugador en su posición.<jupyter_code># Determinar el tiempo de cada jugador en su posición.
player_data['duration'] = player_data['year_end'] - player_data['year_start']
player_data.head()<jupyter_output><empty_output><jupyter_text>d) Castear la fecha de str a objeto datetime<jupyter_code># Castear la fecha de str a objeto datetime
player_data['birth_date_dt'] = pd.to_datetime(player_data['birth_date'], format="%B %d, %Y")
player_data.head()<jupyter_output><empty_output><jupyter_text>e) Determinar todas las posiciones.<jupyter_code># Determinar todas las posiciones.
positions = player_data['position'].unique()
positions<jupyter_output><empty_output><jupyter_text>f) Iterar sobre cada posición y encontrar el mayor valor.<jupyter_code># Iterar sobre cada posición y encontrar el mayor valor
##serie vacia
nba_position_duration = pd.Series()
#iterar
for position in positions:
    #filtramos por posicion
    df_aux = player_data.loc[lambda x: x['position'] == position]
    #encontrar max
    max_duration = df_aux['duration'].max()
    nba_position_duration.loc[position] = max_duration
nba_position_duration<jupyter_output>C:\Users\Mario\miniconda3\envs\mat281\lib\site-packages\ipykernel_launcher.py:3: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  This is separate from the ipykernel package so we can avoid doing imports until<jupyter_text>g) Dermine los jugadores más altos de la NBA<jupyter_code># iteracion jugador mas alto
height_split = player_data['height'].str.split('-')
for player, height_list in height_split.items():
    if height_list == height_list:
        # Para manejar el caso en que la altura sea nan.
        height = int(height_list[0]) * 30.48 + int(height_list[1]) * 2.54
        player_data.loc[player, "height_cm"] = height
    else:
        player_data.loc[player, "height_cm"] = np.nan

max_height = player_data['height_cm'].max()
tallest_player = player_data.loc[lambda x: x['height_cm'] == max_height].index.tolist()
print(tallest_player)<jupyter_output>['<NAME>']
{'fraction_non_alphanumeric': 0.10750124192747143, 'fraction_numerical': 0.017188276204669648, 'mean_word_length': 5.544863459037711, 'pattern_counts': {'":': 3, '<': 63, '<?xml version=': 0, '>': 63, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '8894', 'license': 'MIT', 'n_tokens_mistral': 3636, 'n_tokens_neox': 3559, 'n_words': 1076, 'path': 'homeworksMAT281/C1_data_analysis/03_pandas/03_pandas.ipynb', 'repo_name': 'MariodotR/Aplica', 'size': '10069'}