<jupyter_start><jupyter_text>Elementos básicos de Python - Lenguaje de alto nivel:     en concreto significa que está optimizado para ser leído por personas, más que por máquinas.    - Lenguaje interpretado:     el código Python no es compilado directamente para ser ejecutado por una máquina (lenguaje de máquina). El interpretador de python permite por ejemplo ejecutar código Python dinámicamente (a medida vayamos agregándolo), línea por línea, al contrario de lenguajes como Java o C++, dónde debemos compilar el programa para revisar el resultado. Esto permite una mayor flexibilidad en la construcción del código, apoyando la labor investigativa y su objeto, que en nuestro caso son los datos. (Es posible utilizar Python como lenguaje de scripting, sin embargo, nuestra misión/contexto/objetivo es el análisis y su uso interactivo).    - Lenguaje de tipos dinámicos:     similar a lenguajes como Javascript, no es necesario/requisito que especifiquemos de antemano el tipo de dato que debe tener una variable,  o que por ejemplo una variable de tipo numérica pase luego a ser de tipo string. Debido a que no hay una etapa/paso de compilación, no existe un compilador que nos ayude a manejar los tipo de datos, debido a ello generalmente debemos verificar el correcto funcionamiento del código una vez ejecutado.      Descripciones generales y ejemplos * Tener presente que el código python en este y otros notebooks (jupyter) es ejecutado en modo interactivo, lo que significa que  el código es enviado desde el navegador a una máquina en la nube, la que ejecuta el código y envía de vuelta el/los resultado/s* Para ejecutar el código en el notebook utiliza ctrl ò shift + enter o haz click en Run (botón play)* Asigna valores sin necesidad de especificar su tipo y realiza operaciones con ellos* No es necesario el uso de palabras reservadas como var, let, entre otras.* No es requisito el uso de punto y coma (;) para el final de línea. Python trabaja con espacios e indentación para "entender" el código. (considerando el uso de "end")<jupyter_code>x = 2 
y = 1 

# realiza operaciones con variables anteriormente creadas y asignadas, generando un output (salida/resultado) 
x + y<jupyter_output><empty_output><jupyter_text>El interpretador de python es [stateful](https://es.wikipedia.org/wiki/Estado_(inform%C3%A1tica)) (puede recordar estados pasados), lo que permite utilizar variables entre diferentes celdas del notebook. Recordar que si modificamos parte del código, debemos volver a ejecutarlo para que esos cambios sean reflejados. También es posible ejecutar todas las celdas en una sola acción, lo que nos ahorra tiempo si queremos realizar varias modificaciones.  Funciones en PythonConsideraciones:* Con las funciones de Python no es necesario especificar el tipo de dato que retornamos e incluso no se requiere el uso de la palabra reservada "return". En tal caso, si no hay valores que retornar, python por defecto nos enviará "None", lo que en palabras sencillas representa la ausencia de valor (similar a null en Java).  * Python permite que los parámetros de una función posean valores por defecto. Tener presente que para utilizar esta característica, tales parámetros deben ser declarados al final del conjunto de parámetros de la función.* Con Python es posible asignar funciones a una variable (programación funcional básica)* print() intentará convertir sus argumentos a string e imprimirlos en pantalla. Si bien su uso no es frecuente en el modo interactivo , es útil cuando queremos imprimir múltiples líneas desde una sola celda.<jupyter_code>def sumar_numeros(x,y):
    return x + y
sumar_numeros(1,3)
a = sumar_numeros
a(3,2)<jupyter_output><empty_output><jupyter_text>Tipos y Secuencias * Python posee una función llamada type(), la que permite saber el tipo de dato al cual hace referencia el argumento que le pasamos.* Los tipos poseen propiedades asociadas, las cuales pueden ser datos o funciones<jupyter_code>type('Este es un string')
type(None)
type(1234)
type(3.14)
type(sumar_numeros)<jupyter_output><empty_output><jupyter_text>Colecciones * Tuplas: es una secuencia inmutable  de variables. Esto significa que una tupla almacena items en orden, los cuales no pueden modificarse una vez creada.* Listas: similar a las tuplas una lista almacena items, pero pudiendo modificar su tamaño, número de items y valores de los items. * Diccionarios: tal como las tuplas y listas, los diccionarios almacenan una colección de items/elementos. Sin embargo, la diferencia radica en que a cada item es posible asignarle un nombre/tag y éstos no poseen un orden específico. Esto significa, que si queremos insertar un nuevo elemento al diccionario, debemos especificar su "nombre" o más comúnmente llamada "llave". Así, cada elemento del diccionario se compone de una llave y un valor (esta estructura es equivalente a lo que en otros lenguajes llaman map).<jupyter_code>ejemplo_tupla = ('Pedro', 19345987, 'Angélica', 20987345, 'Mónica', 14098456)
type(ejemplo_tupla)
ejemplo_lista = ['Pedro','Angélica']
type(ejemplo_lista)
ejemplo_diccionario = { 'Pedro': 19345987, 'Angélica': 20987345, 'Mónica': 14098456}
print(ejemplo_diccionario['Pedro'])<jupyter_output>19345987<jupyter_text>* También es posible realizar operaciones en diccionarios<jupyter_code>ejemplo_diccionario['Angélica'] = None
print(ejemplo_diccionario['Angélica'])<jupyter_output>None<jupyter_text>* recorre un diccionario<jupyter_code>for item in ejemplo_diccionario:
    print(ejemplo_diccionario[item])<jupyter_output>19345987
None
14098456<jupyter_text>* obtiene sólo los valores del diccionario<jupyter_code>for item in ejemplo_diccionario.values():
    print(item)<jupyter_output>19345987
None
14098456<jupyter_text>* obtiene "llave/nombre" y valores<jupyter_code>for nombre, rut in ejemplo_diccionario.items():
    print(nombre)
    print(rut)<jupyter_output>Pedro
19345987
Angélica
None
Mónica
14098456<jupyter_text>* Con los diccionarios de Python es posible utilizar una técnica llamada "unpacking" (desempacar). Esta, permite desempacar un diccionario en distintas variables<jupyter_code>diccionario = {'Mónica', 14098456, '<EMAIL>'} 
nombre, rut, email = diccionario

print(nombre)
print(rut)
print(email)<jupyter_output>14098456
<EMAIL>
Mónica<jupyter_text>* También es posible realizar distintas operaciones en una lista<jupyter_code># agrega un nuevo item a la lista
ejemplo_lista.append('Mónica')
print(ejemplo_lista)
# concatena 2 listas
[1,2] + [3,4]
# repite valores de lista la cantidad de veces especificada
[1,2] * 4
# Permite saber si un item se encuentra dentro de una lista
2 in [1,2,3,4]<jupyter_output><empty_output><jupyter_text>* Para Python un string es una cadena de caracteres, lo que permite que las operaciones que hacemos regularmente en una lista, también podamos utilizarlas para strings.<jupyter_code>texto = 'Este es un texto de ejemplo'<jupyter_output><empty_output><jupyter_text>* Cuando queremos obtener una "tajada" de un string o lista, especificamos la posición inicial, seguido de dos puntos y finalmente la posición final. Tener presente que la posición final es excluyente y los espacios en blanco son contabilizados.<jupyter_code>print(texto[0])
print(texto[0:4])
print(texto[3:10])<jupyter_output>E
Este
e es un<jupyter_text>* El index también puede ser negativo, lo que significa que el recorrido se invierte de atrás hacia delante<jupyter_code>print(texto[-1])
print(texto[-7: -2])<jupyter_output>o
ejemp<jupyter_text>* Podemos omitir argumentos, los cuales tendrán su valor por defecto (0)<jupyter_code>print(texto[:4])
print(texto[4:])<jupyter_output>Este
 es un texto de ejemplo<jupyter_text>* Operaciones en strings<jupyter_code>nuevo_texto = texto + '. Segunda parte del texto de ejemplo'
print(nuevo_texto)

print(nuevo_texto*2)
print('ejemplo' in nuevo_texto)<jupyter_output>Este es un texto de ejemplo. Segunda parte del texto de ejemplo
Este es un texto de ejemplo. Segunda parte del texto de ejemploEste es un texto de ejemplo. Segunda parte del texto de ejemplo
True<jupyter_text>* función **split()**: "rompe" el string en subtrings basado en un patrón específico obtieniendo los caracteres desde la posición 0, hasta que encuentre el patrón especificado que en este caso es un espacio en blanco (lo que equivale a obtener la primera palabra del texto)<jupyter_code>print(nuevo_texto.split(' ')[0])<jupyter_output>Este<jupyter_text>* lo mismo para obtener la última palabra<jupyter_code>print(nuevo_texto.split(' ')[-1])<jupyter_output>ejemplo<jupyter_text>* Ambas colecciones (tuplas, listas) son iterables, lo que permite recorrer los elementos/items de cada una.<jupyter_code>for item in ejemplo_lista:
    print(item)<jupyter_output>Pedro
Angélica
Mónica<jupyter_text>* También es posible acceder a items específicos a través del index de cada item, el cual comienza en la posición 0. La función de Python len() permite obtener el tamaño de una lista.<jupyter_code>posicion = 0

while posicion != len(ejemplo_lista):
    print(ejemplo_lista[posicion])
    posicion += 1 # equivale a posicion = posicion +1
    
print('Segundo item de lista: ', ejemplo_lista[1])<jupyter_output>Pedro
Angélica
Mónica
Segundo item de lista:  Angélica<jupyter_text>Más sobre strings en Python:* En Python 3 los strings se basan en Unicode. A principios de la computación los caracteres de un string estaban limitados a uno de 256 valores. Esto era suficiente para representar caracteres Mayúsculas y minúsculas del latín, así como también dígitos individuales (0-9). Este lenguaje que reprensenta estos símbolos es llamado ASCII y como puedes ver, era bien compacto. Sin embargo, los símbolos latinos no bastan para representar muchos otros que existen de otras lenguas, o incluso símbolos de otras áreas del saber, como operadores matemáticos.    A raíz de esta necesidad es que se crea UTF  (Unicode Transformation Format), el cual es capaz de representar más de un millón de distintos caracteres. Este no sólo incluye lenguajes humanos, sino que también símbolos como emojis 😎. Python 3 utiliza UTF por defecto, así no tendremos problema al representar grupos de símbolos multinacionales.        Junto a unicode, python utiliza un lenguaje especial para dar formato a la salida de strings. Como vimos anteriormente, con python no necesitamos explicitar el tipo de dato de una variable, sin embargo, esta ventaja tiene costos. Uno de esos costos es que si el interpretador de Python no es capaz de dar formato automáticamente a una variable, debemos hacer esta conversión manualmente.<jupyter_code># el interpretador no es capaz de dar formato a la concatenacón de un número (int) (lanza error)
print('Mi edad es : ' + 27)
# realizamos la conversión manualmente con la función str()
print('Mi edad es : ' + str(27))
# considerando que tendríamos que repetir esta función códigos fuente de gran envergadura 
# una mejor forma de manejar estos casos sería con el mini lenguaje de formateo de strings de Python:
producto = {'nombre': 'Notebook Levono r78', 'precio': 450000, 'ubicacion': 'pasillo B'}

descripcion_producto = '{} tiene un precio de {} y está ubicado en {}'

# da formato con función format(). Como puedes ver, el orden de los elementos influirá en 
# el string resultante
print(descripcion_producto.format(producto['nombre'], producto['precio'], producto['ubicacion']))

# versión compacta con f'strings
print(f"{producto['nombre']} tiene un precio de {producto['precio']} y está ubicado en {producto['ubicacion']}")<jupyter_output>Notebook Levono r78 tiene un precio de 450000 y está ubicado en pasillo B
Notebook Levono r78 tiene un precio de 450000 y está ubicado en pasillo B<jupyter_text>Manipulación de stringsPython nos brinda distintas herramientas para la manipulación de strings, como:* slice() : recorre un string en busca de patrones, segmentando/extrayendo según la especificación. Esta operación es muy común, por eso recibe el nombre de: regular expression evaluation (evaluación de expresiones regulares), muy útil para tareas de minado de texto por ejemplo.<jupyter_code>slice?<jupyter_output><empty_output><jupyter_text>* split(): éste método retorna una lista con todas las palabras de un string, pudiendo recibir como primer parámetro un argumento/criterio para hacer la división del string.<jupyter_code>string = "Este es un string"
split_1 = string.split("es")
print(split_1)
split_por_espacios = string.split(' ')
print(split_por_espacios)
string.split?<jupyter_output><empty_output><jupyter_text>Más sobre Python y comprensión de listas * Lambdas: es la forma que ofrece Python de crear funciones anónimas. Estas funciones son como cualquier otra, pero su diferencia radica en que no llevan/poseen nombre. Esta característica permite crear funciones simples de una sola línea sin la necesidad de declarar una función completa en la forma convencional. En las funciones lambda sólo se evalúa una sola expresión y su valor es retornado una vez es ejecutada. Nótese también que este tipo de funciones no acepta valores por defecto como parámetros y tampoco podemos agregar lógica compleja/numerosa dentro de ellas, ya que está limitada a sólo una expresión.    ¿Por qué utilizar lambdas si su funcionalidad es limitada en comparación a las funciones estándar?    En nuestro caso, estas funciones resultan muy útiles para tareas de limpieza de datos<jupyter_code>suma_lambda = lambda a, b, c : a + b
suma_lambda(1,2,3)<jupyter_output><empty_output><jupyter_text>SecuenciasEstructuras comunes que generalmente creamos con loops o leyendo un archivo, las cuales permiten iterar sobre sus elementos. Python ofrece una sintaxis abreviada para su creación, llamada Listas por comprensión (List Comprehension).<jupyter_code>numeros_pares = []
for numero in range(0, 100):
    if numero % 2 == 0:
        numeros_pares.append(numero)

numeros_pares<jupyter_output><empty_output><jupyter_text>* Ahora con lista por comprensión, lo que resulta en un código más compacto y generalmente más rápido. No es obligación que utilices este formato, pero sí debes ser capaz de reconocerlo, ya que su uso en ciencia de datos es común.<jupyter_code>numeros_pares = [numero for numero in range(0,100) if numero % 2 == 0]
numeros_pares<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.06170930559460219, 'fraction_numerical': 0.015532752319370256, 'mean_word_length': 5.75, 'pattern_counts': {'":': 0, '<': 88, '<?xml version=': 0, '>': 88, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '175157', 'license': 'MIT', 'n_tokens_mistral': 4825, 'n_tokens_neox': 4707, 'n_words': 1824, 'path': 'Intro Python.ipynb', 'repo_name': 'krystofff/data-science', 'size': '14244'}