Showing preview only (5,949K chars total). Download the full file or copy to clipboard to get everything.
Repository: koldLight/curso-python-analisis-datos
Branch: master
Commit: bb30575b6d1f
Files: 39
Total size: 18.9 MB
Directory structure:
gitextract_ksezc3pd/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── Dockerfile.local
├── LICENSE.txt
├── Pipfile
├── README.md
├── docker-compose.yml
└── notebooks/
├── 01_anaconda_notebooks.ipynb
├── 02_pandas.ipynb
├── 03_programacion.ipynb
├── 04_apis.ipynb
├── 05_web_scraping.ipynb
├── 06_intro_visualizacion.Rmd
├── 06_intro_visualizacion.html
├── 07_graficos.ipynb
├── 08_graficos_seaborn.ipynb
├── 09_graficos_mapas.ipynb
├── 10_numpy.ipynb
├── 11_pivotacion_tablas.ipynb
├── 12_bases_de_datos.ipynb
├── 13_produccion.ipynb
├── 14_entornos.ipynb
├── dat/
│ ├── README.md
│ ├── airquality.csv
│ ├── alquiler-madrid-distritos.csv
│ ├── alquiler-madrid-municipios.csv
│ ├── contaminacion_mad_201812.csv
│ ├── economist-countries-data.csv
│ ├── listings.csv
│ ├── milanuncios.html
│ ├── neighbourhoods.csv
│ ├── neighbourhoods.geojson
│ ├── ufos.csv
│ ├── venta-madrid-distritos.csv
│ └── venta-madrid-municipios.csv
└── soluciones/
├── 02_pandas.ipynb
├── 05_web_scraping.ipynb
├── 11_pivotacion_tablas.ipynb
└── paths.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [koldLight]
================================================
FILE: .gitignore
================================================
.ipynb_checkpoints
completado/
.python-version
================================================
FILE: Dockerfile.local
================================================
FROM python:3.7.6
ENV SOURCE_PATH /curso
WORKDIR $SOURCE_PATH
COPY Pipfile* $SOURCE_PATH/
COPY . .
RUN pip install pipenv
RUN pip install jupyter
RUN pipenv install --dev
RUN pipenv install ipykernel
RUN pipenv run python -m ipykernel install --user --name='libropython'
================================================
FILE: LICENSE.txt
================================================
Attribution-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More_considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
l. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
m. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
================================================
FILE: Pipfile
================================================
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
sklearn = "*"
pandas = "*"
numpy = "*"
seaborn = "*"
requests = "*"
bs4 = "*"
matplotlib = "*"
bokeh = "*"
ipykernel = "*"
folium = "*"
Pillow = ">=8.2.0"
[requires]
python_version = "3.7"
================================================
FILE: README.md
================================================
[](https://mybinder.org/v2/gh/koldLight/curso-python-analisis-datos/master)
# Libro Python para análisis de datos
Este curso tiene como objetivo enseñar Python orientado al análisis de datos en aproximadamente 24 horas.
No hace falta tener conocimientos previos de Python.
## Contenido del curso
* 1: Anaconda y notebooks
* 2: Pandas
* 3: Programación
* 4: APIs
* 5: Web scraping
* 6: Introducción a la visualización de datos
* 7: Gráficos
* 8: Gráficos con seaborn
* 9: Mapas
* 10: Numpy
* 11: Pivotación de tablas
* 12: Bases de datos
* 13: Producción
* 14: Entornos
_Nota:_ Hay gente que piensa que esta forma de ordenar el curso es _rara_. Los programadores estamos acostumbrados a empezar viendo sintaxis básica, tipos de datos, etc... para ir subiendo a librerías de más alto nivel más tarde.
Aquí, lo he hecho al revés. Lo primero que vemos son tablas de datos con pandas y de forma indirecta iremos utilizando números, cadenas de texto, listas, diccionarios, ... sin haberlos dado explícitamente. ¿Por qué? Para que el principiante pueda ver avance rápidamente y comprenda para qué sirve esa _parte aburrida_ de los tipos de datos y estructuras básicas antes de ponerse con ellas.
## Ejecutar los notebooks del curso
### Opción 1: Binder
La forma más fácil es utilizando Binder. Entra [aquí](https://mybinder.org/v2/gh/koldLight/curso-python-analisis-datos/master), espera a que se inicie, abre la carpeta de `notebooks` y tendrás los notebooks disponibles en un entorno funcional. De esta forma, puedes seguir el curso sin necesidad de que instales nada en tu ordenador.
En cambio, si quieres ir realizando los ejercicios y guardando los resultados, te recomiendo que instales Python localmente.
### Opción 2: Instalación local
Para lanzarlo desde tu máquina, tienes dos opciones:
* Instalar Anaconda para Python 3 (recomendado para usuarios principiantes)
* Utilizar `pipenv` (recomendado para usuarios avanzados).
#### Opción 2a: Instalación local con Anaconda
Instala la última versión de Anaconda para Python 3.7 para tu sistema operativo desde [aquí](https://www.anaconda.com/download). Fíjate bien que instales la de Python 3.7 (y no la de Python 2.7).
Aquí tienes un tutorial paso a paso de cómo hacerlo:
* [Windows](https://www.datacamp.com/community/tutorials/installing-anaconda-windows). Fíjate bien en el paso en el que debes marcar que añada anaconda al PATH. Esa casilla debe quedar marcada.
* [Mac](https://www.datacamp.com/community/tutorials/installing-anaconda-mac-os-x).
Una vez instalado, nuestra terminal debería reconocer los comandos `conda` (el gestor de paquetes), `jupyter` (la herramienta de notebooks) y `python`, gracias a que hemos añadido anaconda a nuestro PATH. Esto quiere decir que, cuando escribamos uno de estos comandos, nuestro sistema lo buscará en la carpeta de anaconda.
Para comprobarlo, abre una consola. Esto se puede hacer:
* En Windows, abriendo el menú inicio y escribiendo cmd.exe. Si necesitas ayuda, mira más opciones [aquí](https://www.lifewire.com/how-to-open-command-prompt-2618089).
* En Mac, abre una terminal. Si necesitas ayuda sobre cómo abrirla, mira [aquí](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line).
* En ubuntu / debian, abre una terminal. Si necesitas ayuda, mira [aquí](https://www.lifewire.com/ways-to-open-a-terminal-console-window-using-ubuntu-4075024).
Una vez abierto, nos debería funcionar bien los siguientes tres comandos:
```
python --version
conda --version
jupyter --version
```
Si nos dice que no reconoce el comando, es que no se ha añadido correctamente anaconda al PATH. Los tutoriales referenciados tienen buenas soluciones a este problema habitual.
Ahora, vamos a instalar las dependencias necesarias para nuestras sesiones con `conda`. Para ello, abre una terminal en tu ordenador y lanza lo siguiente:
```
conda install -c conda-forge seaborn
conda install -c conda-forge folium
```
Los comandos anteriores deberán instalar sin error las librerías.
Y finalmente, para abrir los notebooks en Windows, puedes hacer, desde la consola (cmd.exe):
```
jupyter notebook --notebook-dir='C:\mi\ruta\con\notebooks\'
```
En mac o linux, con la terminal:
```
jupyter notebook --notebook-dir='/mi/ruta/con/notebooks'
```
Modificando la ruta dependiendo de dónde los hayas descargado.
#### Opción 2b: Instalación local con pipenv
Necesitarás:
* Python 3.7 o superior
* pipenv
* jupyter
Si no tienes `pipenv`, puedes instalarlo como comando así:
```
sudo -H pip install -U pipenv
```
Si no tienes `jupyter`, puedes instalarlo como comando así:
```
sudo -H pip install -U jupyter
```
Para instalar las dependencias, ejecuta:
```
pipenv install --dev
```
La primera vez que lo lances, creará el entorno virtual con las dependencias del proyecto.
Y para crear el kernel sobre el que correrán los notebooks, lanza lo siguiente:
```
# Activate the virtual environment
pipenv shell
# Create the kernel
python -m ipykernel install --user --name='libropython'
# Exit the virtual environment shell
exit
```
A partir de ahora, en `jupyter`, tendrás un nuevo kernel disponible llamado `libropython`. Tendrás que seleccionarlo para ejecutar correctamente los notebooks de este curso.
Finalmente, para lanzar los notebooks:
```
pipenv run jupyter notebook notebooks
```
### Opción 3: Docker
Si prefieres utilizar un contenedor para lanzar el curso, basta con que tengas instalado `docker` en tu ordenador y lances:
```
docker-compose up --build
```
Cuando esté corriendo, saldrá un mensaje parecido a este por consola:
```
curso_1 | To access the notebook, open this file in a browser:
curso_1 | file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
curso_1 | Or copy and paste one of these URLs:
curso_1 | http://d9a5de8c039a:8888/?token=xxxxxxxx
curso_1 | or http://127.0.0.1:8888/?token=xxxxxxxx
```
Copia y pega en tu navegador la última URL (la que empieza por `http://127.0.0.1:8888/?token=`) y accederás a los notebooks desplegados en tu contenedor.
## Contribuye
Si ves algo incorrecto, que no se entiende bien, crees que falta alguna explicación o tienes alguna idea que compartir, puedes crear una `issue` en el repositorio.
## Licencia
[](http://creativecommons.org/licenses/by-sa/4.0/)
Puedes utilizar libremente este material, con las siguientes condiciones:
* Atribuir la autoría a este repositorio.
* Si lo utilizas y haces cambios, deberás liberarlo también bajo la misma licencia.
## TODO
* Revisar links rotos
* Añadir ejemplo paleta cut en sns
* Pasar tema 06 intro visualización a notebook (en lugar de Rmd + html)
================================================
FILE: docker-compose.yml
================================================
version: '3.7'
services:
curso:
build:
context: .
dockerfile: Dockerfile.local
volumes:
- .:/curso
ports:
- 8888:8888
command: pipenv run jupyter notebook notebooks --ip='0.0.0.0' --allow-root
================================================
FILE: notebooks/01_anaconda_notebooks.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Anaconda y _notebooks_"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"## Anaconda"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Anaconda es una distribución de Python autocontenida y aislada del resto del sistema operativo especialmente pensada para el análisis de datos y la computación científica. Incluye, además de Python, una serie de paquetes y herramientas utilizadas en análisis de datos como:\n",
"\n",
"* El gestor de paquetes `conda`\n",
"* Jupyter _notebooks_\n",
"* El IDE Spyder\n",
"* Una selección de paquetes muy usados en ciencia de datos y aplicaciones científicas como `pandas`, `scikit-learn`, `scipy`, `numpy`, `matplotlib`,...\n",
"\n",
"Más información sobre Anaconda [aquí](https://docs.anaconda.com/anaconda/)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## _Notebooks_"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Los _notebooks_ son documentos interactivos que combinan texto y código. Los usos principales son:\n",
"\n",
"* IDE: son una plataforma de desarrollo de código\n",
"* Documentación de código\n",
"* Presentación y diseminación de resultados\n",
"\n",
"Los _notebooks_ incorporan a Python los principios de la [_programación literaria_](https://en.wikipedia.org/wiki/Literate_programming) y [investigación reproducible](http://t-redactyl.io/blog/2016/10/a-crash-course-in-reproducible-research-in-python.html).\n",
"\n",
"Un _notebook_ es esencialmente una secuencia de bloques o celdas, que pueden ser de dos tipos: texto y código. En el menú superior hay un botón (con el símbolo de un teclado) que indica las combinaciones de teclas con las que se pueden crear, eliminar, editar, ejecutar, etc. los bloques.\n",
"\n",
"Los _notebooks_ se abren en el navegador. Nuestros _notebooks_ apuntan a un servidor que corre en local, pero es posible abrir _notebooks_ que corren en servidores remotos."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Bloques de texto"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Los bloques de texto permiten crear títulos de distinto nivel; enumeraciones; texto corrido; marcas tipográficas como _cursivas_, **negritas** o `de código`, etc. Además de eso, se pueden escribir bloques de código como\n",
"\n",
"```\n",
"for (i %in% 1:10)\n",
" print(\"R rocks!)\n",
"```\n",
"\n",
"Para saber más opciones de la sintaxis, mira [esta guía de `markdown`](https://www.markdownguide.org/basic-syntax/)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Bloques de código"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Los bloques de código contienen... código. Al ejecutarlo, se muestra el resultado de la última instrucción.\n",
"\n",
"El encargado de ejecutar el código es un _kernel_. Usaremos un _kernel_ de Python, pero es posible asociar a un _notebook_ _kernels_ en otros lenguajes de programación (p.e., Julia o R)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Un ejemplo de celda con código\n",
"a = 4\n",
"b = 3\n",
"a + b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cuando devolvemos una tabla en una celda, se visualiza con formato:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn import datasets\n",
"import pandas as pd\n",
"import numpy as np\n",
"\n",
"iris_data = datasets.load_iris()\n",
"\n",
"iris = pd.DataFrame(iris_data.data,columns=iris_data.feature_names)\n",
"iris['species'] = pd.Series(iris_data.target_names[iris_data.target])\n",
"iris.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"También podemos incluir gráficos devolviéndolos en celdas de código"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Con esta instrucción, indicamos que queremos ver incrustados los gráficos dentro del notebook\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"iris.plot.scatter(x='petal length (cm)',\n",
" y='petal width (cm)')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ejercicios\n",
"\n",
"1. Crea un nuevo _notebook_ y añádele bloques de texto y código.\n",
"2. Consulta los atajos de teclado de Jupiter (botón con la imagen de un teclado en el menú) y familiarízate (y usa) los más comunes (crear bloque antes/después, borrar bloque, cambiar el tipo de bloque, ejecutar bloque, etc.)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "libropython",
"language": "python",
"name": "libropython"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: notebooks/02_pandas.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Introducción a Pandas"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"Pandas es una librería de Python utilizada para tratar datos en forma de tabla. Nos permite importar, exportar y hacer las operaciones habituales que nos permiten otras herramientas como Excel o el lenguaje SQL"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Importación y exportación\n",
"\n",
"Podemos importar datos a DataFrames de Pandas de diferentes orígenes y formatos, entre ellos:\n",
"\n",
"* De CSV: con [`read_csv`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)\n",
"* De Excel: con [`read_excel`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html)\n",
"* De base de datos: con [`read_sql`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_sql.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como ejemplo, vamos a importar unos datos que tenemos en CSV con precios de alquileres en los distritos de Madrid:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"alquiler = pd.read_csv('dat/alquiler-madrid-distritos.csv', index_col=False)\n",
"alquiler.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"También podemos exportar esos datos a CSV haciendo:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.to_csv('alquiler.csv')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio \n",
"\n",
"Comprueba que se ha guardado el fichero correctamente. Antes de nada, tendrás que ubicarlo en tu disco duro. Debería estar en el directorio en el que se guardan los _notebooks_."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Nota\n",
"\n",
"Si te interesa saber cómo lanzar consultas a una base de datos usando SQL, puedes leer [este tutorial](https://www.pybonacci.org/2015/03/17/pandas-como-interfaz-sql/) (y los enlaces que contiene)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Usa la función [`read_excel`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html) para importar a Python alguna tabla de tu interés"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Estructura básica e inspección\n",
"\n",
"Las tablas en `pandas` son objetos de la clase `DataFrames`. Un `DataFrame` consta de dos partes: un índice y los datos propiamente dichos. Las columnas de los datos son de la clase `Series`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para consultar las columnas de un `DataFrame`, accedemos a la propiedad `columns`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.columns"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Si además queremos saber el tipo del dato, accedemos a la propiedad `dtypes`.\n",
"\n",
"*Nota*: las cadenas de texto se marcan como `object` dentro de un DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.dtypes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cada `DataFrame` tiene un índice. Si no lo hemos especificado, será un incremental sin relación con nuestros datos. El uso de índices está recomendado cuando tratamos con datos grandes, ya que permite acceder a las filas por _hash_ en lugar de tener que iterar por todas ellas para encontrar el valor que se busca. Los índices también son importantes a la hora de realizar agregaciones y cruces entre tablas.\n",
"\n",
"Para consultar cuál es el índice de un DataFrame, accedemos a la propiedad `index`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.index"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos alterarlo con `set_index`. El nuevo índice puede ser una o varias columnas."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler_nuevo_indice = alquiler.set_index(['distrito', 'ano', 'quarter'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Una forma rápida de echar un vistazo a los datos es consultas las primeras o últimas filas del DataFrame, con las funciones `head` y `tail`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.tail()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos seleccionar un listado de columnas a devolver de la siguiente forma:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler[['distrito', 'precio']].head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para conocer el número de filas de una tabla hay varias opciones:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(alquiler)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Nota\n",
"\n",
"El índice no forma parte propiamente de los datos:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler_nuevo_indice.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"* Carga en un dataframe de pandas el csv `dat/alquiler-madrid-municipios.csv` en una variable que se llame `alquiler_municipios`\n",
"* Examina las primeras y últimas filas\n",
"* Extrae el número de filas y columnas"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Filtro y selección"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hay tres operadores fundamentales para seleccionar filas y columnas: `loc`, `iloc` y `[]`. La diferencia fundamental entre `loc` e `iloc` es que el primero requiere _etiquetas_ y el segundo, índices numéricos (la `i` inicial viene de `integer`).\n",
"\n",
"\n",
"### Selección por índices numéricos\n",
"\n",
"Para acceder por posición usando índices numéricos, se usa `iloc[]`, como en los siguientes ejemplos:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# por defecto, seleccionamos filas\n",
"alquiler_nuevo_indice.iloc[200]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# pero también se pueden seleccionar filas y columnas\n",
"# además, usando rangos\n",
"alquiler.iloc[3:5, 1:]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# índices no consecutivos\n",
"# recuerda: en python, se empieza a contar en 0\n",
"alquiler.iloc[[1, 2, 4], [0, 3]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# los índices negativos indican que se empieza a contar desde el final\n",
"alquiler.iloc[-3:-1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"* Muestra las primeras 5 filas usando `iloc`\n",
"* Muestra las últimas 5 filas usando `iloc`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Selección por etiquetas\n",
"\n",
"Para acceder por _etiquetas_ (es decir, columnas parte del índice), se usa `loc[]`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler_nuevo_indice.loc[('Centro', 2014, 2)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# O un distrito completo\n",
"alquiler_nuevo_indice.loc[('Centro')].head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Muestra sobre `alquiler_nuevo_indice` las filas para distrito `Retiro` y año 2012."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Selección por condiciones\n",
"\n",
"Para extraer las filas que cumplen una condición, le pasamos al DataFrame una Series de booleanos, o directamente algo que la devuelva."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler[alquiler.distrito == 'Retiro'].head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Nota:** mira cómo en el código anterior hemos seleccionado la columna `distrito` usando un punto, `alquiler.distrito`. Es una alternativa a la notación mediante corchetes, `alquiler['distrito']`.\n",
"\n",
"**Para profundizar:** puedes leer las diferentes ventajas e inconvenientes de la notación punto con respecto a la de corchetes [aquí](https://www.dataschool.io/pandas-dot-notation-vs-brackets/) y decidir cuál prefieres utilizar.\n",
"\n",
"Podemos combinar varias condiciones con `&` (y lógico) y `|` (o lógico)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# No olvides los paréntesis, es importante por prioridad de operandos!\n",
"\n",
"alquiler[(alquiler.distrito == 'Retiro') & (alquiler.ano == 2012)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Extrae los nombres de los distritos cuyo precio por metro cuadrado es superior a 15€ en el año y trimestre más reciente del que tenemos datos (míralo imprimiento las últimas filas de la tabla)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 1. Imprime las últimas filas de la tabla"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 2. Haz el filtro. Una vez hecho, saca solo la columna distrito"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ordenación\n",
"\n",
"Podemos ordenar un DataFrame por una o varias columnas, de forma ascendente o descendente, con [`sort_values`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sort_values.html)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.sort_values('distrito', ascending=True).head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.sort_values(['ano', 'quarter', 'distrito'], ascending=[False, False, True]).head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Extrae de mayor a menor por precio las filas de la tabla para Tetuán a partir del año 2017."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Transformación\n",
"\n",
"Nuevas columnas calculadas, cambio de tipo de dato, eliminar una columna\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Crear una columna calculada\n",
"\n",
"Podemos operar sobre las columnas para crear otras nuevas"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Hago una copia para no modificar el dataframe original\n",
"alquiler_2 = alquiler.copy()\n",
"\n",
"alquiler_2['precio_90m'] = alquiler_2.precio * 90\n",
"alquiler_2.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Las operaciones que no se pueden lanzar directamente sobre la `Series` completa, la ejecutamos por elemento utilizando `apply`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Fíjate bien en la función lambda, es una función en una sola línea\n",
"alquiler_2['ano_quarter'] = alquiler_2.apply(lambda fila: str(fila.ano) + 'Q' + str(fila.quarter), axis=1)\n",
"alquiler_2.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Crea una nueva columna `precio_120m` sobre `alquiler_2` que represente el precio de 120 metros cuadrados, pero utilizando `apply` y una función `lambda`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Renombrar una columna\n",
"\n",
"Fíjate en la documentación de `rename` y sus ejemplos, [aquí](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.rename.html).\n",
"\n",
"Por ejemplo, para renombrar la columna `precio_90m` a `precio_90_metros` sería:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler_2 = alquiler_2.rename(columns={'precio_90m': 'precio_90_metros'})\n",
"alquiler_2.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Fíjate en el ejemplo anterior. En general, las funciones de `pandas` crean un nuevo objeto con el resultado de la operación, pero no modifican el actual. En estas funciones, suele haber un parámetro `inplace` con valor por defecto a `False`. Si lo ponemos a `True`, la operación se realiza sobre el objeto que pasamos por parámetro."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### La importancia de la nomenclatura\n",
"\n",
"Tener buenos nombres de columnas en un DataFrame es importante. Hará mucho más legible nuestro código si nuestras columnas tienen nombres descriptivos, sin caracteres extraños y separados por `_`.\n",
"\n",
"Unos cuantos ejemplos de malos nombres:\n",
"\n",
"* `col1`, `col2`, ..., `colN`: no sabemos qué es cada cosa.\n",
"* `precio euros`, `metros cuadrados`: los espacios dificultan escribir código. Por ejemplo, ya no podremos acceder a las columnas con la notación `dataframe.columna`.\n",
"* `año`, `variación`, `precio_€`: los caracteres no-asciii (que no son letras no acentuadas ni números) pueden dar problemas al compartir código (p.e. entre Linux y Windows), al exportar / importar, etc. Es mejor evitarlos.\n",
"* `PrecioEuros`, `MetrosCuadrados`: aunque es más sutil, el estándar en Python es escribir en snake_case. Es decir, utilizando minúsculas y usando `_` para separar palabras.\n",
"\n",
"Ejemplo de buenos nombres:\n",
"\n",
"* `distrito`, `precio_euros`, `metros_cuadrados`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Eliminar una columna\n",
"\n",
"Podemos utilizar [`drop`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.drop.html).\n",
"\n",
"Por ejemplo:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler_2 = alquiler_2.drop(columns=['precio_90_metros'])\n",
"alquiler_2.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Cambiar el tipo de dato\n",
"\n",
"Vamos a crear un DataFrame muy simple para verlo."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"prueba = pd.DataFrame({'precio': ['10.50', '15.35', '22.15']})\n",
"prueba"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"prueba.dtypes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tenemos un DataFrame con precios, pero estos son cadenas de texto en lugar de números. Esto va a limitar nuestro análisis: no podremos ejecutar operaciones aritméticas, calcular medias, etc.\n",
"\n",
"Es muy habitual que esto pase en el momento de cargar unos datos. Veremos más adelante cómo solucionar problemas al cargar CSVs de habla española, que utilizan la coma como separador decimal en lugar del punto, que es el que entiende Python (y, en general, todos los lenguajes de programación).\n",
"\n",
"Sobre nuestro ejemplo actual `prueba`, podemos usar [`astype`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.astype.html) para convertir la columna a numérica. En este caso, como es numérica con decimales, usaremos el tipo `float`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"prueba['precio'] = prueba.precio.astype(float)\n",
"prueba.dtypes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Resumen estadístico\n",
"\n",
"Pandas provee una serie de funciones de resumen estadístico que podemos aplicar sobre una columna concreta, o sobre todas las del DataFrame."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para un resumen para todas las columnas de número de filas, media, desviación estándar, cuartiles, ... usamos [`describe`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.describe.html)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.describe()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos utilizar también `sum`, `mean`, `std`, `count`, `min`, `max`, ... sobre el DataFrame o una columna en concreto"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.mean()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.precio.max()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Extrae los cuantiles 0.1 y 0.9 del precio para el distrito `Tetuan`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Agrupación\n",
"\n",
"De una forma equivalente a como hacemos en SQL, podemos agregar las tablas y sacar resúmenes de los grupos. La operación en pandas se hace en dos fases:\n",
"\n",
"* El `groupby`: donde especificamos la o las columnas por las que agregar\n",
"* La aplicación de la función de agregación sobre una o varias columnas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Un resumen usando una función de agregación sobre todas las columnas del DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.groupby('ano').max()\n",
"\n",
"# Atención, fíjate bien en lo que hace esto. Saca el valor máximo de distrito (alfabéticamente),\n",
"# de quarter y precio (numéricamente), pero no representa filas completas\n",
"# Es decir, Villaverde en el quarter 4 no tuvo ese precio"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para hacerlo únicamente sobre una columna:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler.groupby('ano').precio.min()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para aplicar diferentes resúmenes sobre diferentes columnas"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tmp = alquiler.groupby('ano').agg({'precio': 'mean', 'distrito': 'first'})\n",
"tmp.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Extrae el precio máximo histórico para cada distrito a partir del 2010"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Puedes ver más información sobre agrupaciones en la [documentación de pandas](https://pandas.pydata.org/pandas-docs/stable/groupby.html). Es especialmente útil la parte sobre transformaciones."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Cruce\n",
"\n",
"Podemos cruzar dos tablas por una o varias columnas en pandas, de forma equivalente a como hacemos en SQL, con [`merge`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.merge.html). También podemos usar los distintos tipos de cruce:\n",
"\n",
"* `inner`: para obtener solamente los registros que crucen en ambas tablas\n",
"* `left` o `right`: para mantener los registros de una de las dos tablas, crucen o no con la otra\n",
"* `outer`: para manter los registros de ambas tablas, crucen o no"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Aquí, además, un ejemplo de cómo crear un dataframe a partir de un diccionario\n",
"df_ejemplo = pd.DataFrame({'distrito': ['Moratalaz', 'Centro', 'Barajas'],\n",
" 'poblacion': [95000, 150000, 46000]})\n",
"df_ejemplo"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tmp = df_ejemplo.merge(alquiler, on='distrito')\n",
"tmp.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(tmp)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tmp = df_ejemplo.merge(alquiler, on='distrito', how='right')\n",
"tmp.tail()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(tmp)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"* Carga en un DataFrame el CSV `dat/venta-madrid-distritos.csv`\n",
"* Crúzalo con el que ya tenemos de alquiler. El objetivo es tener, para cada distrito, año y cuatrimestre, tanto el precio de alquiler como el de venta del metro cuadrado. Para saber cómo poner sufijos a las columnas que colisionan en el cruce, mira la documentación de `merge`.\n",
"* Extrae los precios medios de venta y alquiler por distrito para todo el histórico\n",
"* Extrae, para el año y cuatrimestre más reciente del que haya datos, el distrito donde es más rentable comprar una vivienda para destinarla a alquiler. Es decir, con el ratio precio alquiler / precio venta más alto."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "libropython",
"language": "python",
"name": "libropython"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: notebooks/03_programacion.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Introducción a la programación en Python\n",
"\n",
"Python es un lenguaje de programación. En los _notebooks_ previos, casi todo el trabajo que hemos realizado es prácticamente interactivo, aunque en un par de ocasiones nos hemos visto obligados a utilizar funciones o bucles.\n",
"\n",
"En este _notebook_ aprenderemos los rudimentos de la programación en Python, comenzando con tipos de datos y terminando con la definición de funciones simples."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tipos de datos"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### _Strings_\n",
"\n",
"Vamos a ver una serie de funciones útiles para operar con _strings_, i.e., texto, en Python. Las operaciones básicas con cadenas son las de concatenar, partir en _tokens_, buscar y reemplazar.\n",
"\n",
"Las operaciones con cadenas de texto en Python son similares a las de otros lenguajes de programación."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`len()` nos devuelve la longitud (número de caracteres) de la cadena de texto:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len('Python')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Las cadenas de texto también se pueden _seccionar_ usando los corchetes:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"a = 'Python'\n",
"b = a[2:4]\n",
"b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para separar una cadena por uno o varios caracteres, creando una lista con el resultado, usamos `split()`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"profesiones = 'analista,consultor informático,jefe de proyecto'.split(',')\n",
"profesiones"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La operación complementaria a `split()` es `join()`, que une los elementos de una lista en una cadena de texto"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"','.join(profesiones)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aunque también se puede concatenar usando `+` entre dos cadenas de texto:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"my_file = 'procesar.py'\n",
"print('Error en el programa ' + my_file)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Nota:** si usas `+` entre una cadena de texto y un tipo diferente de dato, fallará. Por ejemplo, `3 + ' €'` es incorrecto. En su lugar, convierte el tipo de dato antes, haciendo: `str(3) + ' €'`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`strip()` elimina espacios en blanco delante y detrás"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"' hola '.strip()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`lower` y `upper` pasan todo a minúsculas o mayúsculas"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"'pyThon'.lower()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Si tenemos una cadena de texto que puede ser transformada a entero, podemos usar `float()` para decimales e `int()` para enteros"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n = float('3.1416')\n",
"print(n, type(n))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n = int('531')\n",
"print(n, type(n))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`replace()` sustituye trozos de cadenas por otros"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"'Hola NOMBRE'.replace('NOMBRE', 'paquito')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Sobre el siguiente DataFrame, crea dos columnas separadas, una para el importe (que sea float) y otra para la divisa (que sea cadena de texto)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"importes = pd.DataFrame({'importe_divisa': ['592,50 EUR', '690,10 USD', '2951 GBP']})\n",
"importes"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Expresiones regulares\n",
"\n",
"Tanto en Python como en casi todos los lenguajes de programación, trabajar _en serio_ con cadenas de texto implica usar [_expresiones regulares_](https://es.wikipedia.org/wiki/Expresi%C3%B3n_regular). Las expresiones regulares permiten buscar patrones en texto, reemplazar y realizar muchas operaciones avanzadas sobre cadenas de caracteres.\n",
"\n",
"Consulta [esta guía](https://www.dataquest.io/blog/regex-cheatsheet/) acerca del uso de las expresiones regulares en Python."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import re"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Búsqueda\n",
"\n",
"`re.findall` sirve para encontrar todas las subcadenas que coinciden con un patrón.\n",
"\n",
"Por ejemplo, si queremos encontrar todos los números de un texto:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cadena = 'Compra de 13 bolígrafos y 200 folios'\n",
"re.findall(r'[0-9]+', cadena)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O todos los emails:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cadena = 'To: lola@dominio.com; Cc: rosa.garcia@otracosa.com; From: ana_perez@blabla.es'\n",
"re.findall(r'[0-9a-z._]+@[0-9a-z._]+', cadena)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Sustitución\n",
"\n",
"`re.sub` sustituye un patrón por una cadena de texto. Se suele utilizar para:\n",
"\n",
"* Eliminar o reemplazar partes no deseadas del texto\n",
"* Quedarnos solo con la parte que nos interesa\n",
"\n",
"Además, esta función nos permite meter en el reemplazo partes del texto original. Esto lo podemos hacer gracias a la selección de grupos.\n",
"\n",
"Un ejemplo para eliminar partes no deseadas habitual es buscar los números decimales separados por comas, utilizando grupos para seleccionar la parte entera y la decimal:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cadena = '13 bolígrafos, precio: 10,25€. 200 folios, precio 4,35€'\n",
"re.sub(r'([0-9]+)\\,([0-9]+)€', r'\\1.\\2€', cadena)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Y otro ejemplo, para quedarnos solo con la parte que nos interesa. Para eliminar lo _no interesante_ tendremos que hacer que nuestra expresión regular haga match con la cadena entera. Esto lo podemos conseguir jugando con `^` (inicio de candea), `$` (final de cadena) y wildcards como `.` (hace match con cualquier carácter).\n",
"\n",
"En este caso, queremos quedarnos solo con el email. Vamos a usar dos trucos:\n",
"\n",
"* Seleccionar en un grupo la parte con la que nos queremos quedar en la cadena\n",
"* Hacer match con el resto, para eliminarlo al reemplazar"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cadena = 'Ana Pérez, 42 años, ana_perez@blabla.es, ingeniera aeroespacial'\n",
"re.sub(r'^.*([0-9a-z._]+@[0-9a-z._]+).*$', r'\\1', cadena)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"¡Tenemos un problema! Las expresiones regulares son hambrientas de izquierda a derecha. Como el patrón `.*` también hace match con el principio del email, se lo come. Esto se suele solucionar forzando un parón entre el patrón hambriento y el que queremos que haga match realmente.\n",
"\n",
"Vamos a pararlo con un conjunto _inverso_ al que tiene que hacer match. Para hacer el conjunto inverso, utilizamos `[^...]`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cadena = 'Ana Pérez, 42 años, ana_perez@blabla.es, ingeniera aeroespacial'\n",
"re.sub(r'^.*[^0-9a-z._]([0-9a-z._]+@[0-9a-z._]+).*$', r'\\1', cadena)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Con expresiones regulares, realiza los siguientes ejercicios:\n",
"\n",
"Sobre la cadena `'30 del 04 para el 2018'`, extrae:\n",
"\n",
"* Todos los números de forma independiente. Es decir, resultará una lista con 3, 0, 0, 4, ...\n",
"* Todos los números de forma consecutiva. Es decir, resultará una lista con 30, 04, 2018.\n",
"* Extrae las palabras de `'30 del 04 para el 2018'`. Saldrá del, para, ...\n",
"* Extrae el último número consecutivo. Saldrá 2018.\n",
"* Transfórmala para convertirla a formato ISO (es decir, yyyy-mm-dd)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Listas\n",
"\n",
"Las listas son contenedores de valores. Pueden ser de diferentes tipos de dato, aunque generalmente se usan para datos homogéneos (p.e., una lista de números).\n",
"\n",
"Las operaciones que se realizan más habitualmente con listas son:\n",
"\n",
"* Extrer elementos (p.e., los 10 primeros o los 5 últimos).\n",
"* Añadir y borrar elementos.\n",
"* Concatenar listas (con `+`)\n",
"* Ordenar listas\n",
"* Operaciones _funcionales_ clásicas:\n",
" * Map: aplicar una función a cada elemento\n",
" * Reduce: obtener un agregado (longitud, suma, media, etc.)\n",
" * Filter: obtener una sublista a partir de otra dada de acuerdo con algún criterio"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ejemplo de creación de una lista"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"precios = [2300, 1942, 3455, 4100, 600, 1230]\n",
"precios"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para conocer la longitud de la lista, utilizamos `len`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(precios)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para extrar un elemento en una determinada posición, ponemos entre corchetes el índice"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"precios[2]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Si usamos índices negativos, extraemos elementos contando desde la derecha (-1 es el último elemento)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"precios[-4:-2]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para extraer un rango:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"precios[:3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Algunas funciones estadísticas como `min`, `max` y `sum` vienen cargadas por defecto. Con el paquete `statistics`, además, podemos calcular medianas, medias, etc. sobre una lista."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"min(precios)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from statistics import mean, median\n",
"\n",
"mean(precios)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para evaluar si un elemento está contenido en una lista, usaremos `in` o `not in`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"600 in precios"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para añadir un nuevo elemento, usamos `append` (esta función es _inplace_, es decir, modifica el objeto en lugar de devolver una nueva lista)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"precios_2 = precios.copy()\n",
"precios_2.append(650)\n",
"precios_2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Nota:** Existe un módulo de Python, `numpy` que implementa sus propias listas (o `arrays`), más orientadas al cálculo numérico y matricial que son extensiones de estas listas genéricas. A su vez, las columnas de un `DataFrame` de `pandas` son extensiones de los `arrays` de `numpy`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### List comprehensions\n",
"\n",
"Son una forma concisa de iterar y operar sobre listas que combinan las operaciones _map_ y _filter_."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"[f'{precio} €' for precio in precios]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Nota:** fíjate en como funciona `f''`. Sirve para crear cadenas de texto mezclando texto normal y valor de variables"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"También permiten filtrar elementos añadiendo un bloque con `if`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"[f'{precio} €' for precio in precios if precio > 2000]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Por lo indicado más arriba, las _list comprehensions_ funcionan también sobre columnas de `DataFrames`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alquiler = pd.read_csv('dat/alquiler-madrid-distritos.csv', index_col=False)\n",
"\n",
"alquiler[\"precio_90_m\"] = [90 * precio for precio in alquiler.precio]\n",
"# Nota: lo mismo puede obtenerse haciendo, como antes,\n",
"# alquiler[\"precio_90_m\"] = 90 * alquiler.precio\n",
"\n",
"alquiler[\"trimestre\"] = [str(ano) + \"Q0\" + str(quarter) \n",
" for ano, quarter in alquiler[['ano', 'quarter']].values]\n",
"\n",
"alquiler.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Crea una lista que contenga las palabras de `frase` que tengan una longitud de más de 5 caracteres."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"frase = 'Estoy en el curso de python para ciencia de datos'"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Elimina las vocales de la frase anterior"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Calcula la lista de los cuadrados de:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"lista = [2, 3, 5, 7]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Diccionarios\n",
"\n",
"Los diccionarios son una colección de elementos clave-valor:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"poblacion = {'Moratalaz': 95000,\n",
" 'Centro': 150000,\n",
" 'Barajas': 46000}\n",
"poblacion"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para acceder a un elemento, podemos usar:\n",
"\n",
"* Los corchetes\n",
"* La función `get`\n",
"\n",
"La diferencia es que get devuelve None en lugar de lanzar un error en caso de que la clave no exista"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"poblacion['Barajas']"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"poblacion.get('Tetuan')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Dict comprehensions\n",
"\n",
"Es el equivalente de las list comprehensions en diccionarios"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Atención al .items() para poder iterar sobre clave y valor en el dict\n",
"\n",
"{distrito: (valor / 1000) for distrito, valor in poblacion.items()}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"A partir del diccionario `precios_por_distrito`, crea un diccionario donde la clave sea el distrito y el valor, otro diccionario con dos elementos: el precio mínimo (la clave será `minimo`) y el máximo (con clave `maximo`)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"venta = pd.read_csv('dat/venta-madrid-distritos.csv', index_col=False)\n",
"venta = venta[venta.precio.notnull()]\n",
"precios_por_distrito = venta.groupby('distrito').precio.apply(lambda precios: precios.tolist()).to_dict()\n",
"precios_por_distrito"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Fechas\n",
"\n",
"En Python existen dos tipos de datos para tratar las fechas:\n",
"\n",
"* `date` si son referencias sin hora\n",
"* `datetime` si incluyen la hora\n",
"\n",
"Podemos crearlas de la siguiente forma"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from datetime import date, datetime\n",
"\n",
"fecha = date(2019, 1, 30)\n",
"fecha"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fecha_hora = datetime(2019, 3, 30, 14, 35, 59)\n",
"fecha_hora"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos convertir (o truncar si aplica) una fecha más hora a solo fecha con `.date()`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fecha_hora.date()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tenemos dos funciones para pasar de cadena de texto a fecha y al revés:\n",
"\n",
"* `datetime.strftime()`: de fecha a cadena de texto (la f es de format)\n",
"* `datetime.strptime()`: de cadena de texto a fecha (la p es de parse)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fecha.strftime('%Y-%m-%d')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fecha_hora.strftime('%Y-%m-%d %H:%M:%S')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Los símbolos que puedes usar los puedes consultar [aquí](http://strftime.org/)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Formatea fecha_hora con formato 12 horas en lugar de 24 e indicando si es AM o PM"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Parsea a fecha:\n",
"\n",
"* `'20/05/2018'`\n",
"* `'2018-05-20'`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos sumar o restar periodos (p.e. días) con `timedelta`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from datetime import timedelta\n",
"\n",
"fecha + timedelta(days=5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Funciones\n",
"\n",
"Durante el desarrollo de este curso, hemos definido varias funciones `lambda`. Son funciones pequeñas, típicamente _oneliners_, pensadas para hacer operaciones simples en una línea. Pero frecuentementese se hace necesario desarrollar transformaciones más complejas.\n",
"\n",
"Comenzaremos viendo cómo definir funciones y, después, cómo añadirles expresiones de control de código.\n",
"\n",
"### Definición de funciones\n",
"\n",
"La definción de una función comienza con `def` y termina con `:`. El cuerpo de la función está indentado y la definción de la función termina ahí donde termina la indentación. De hecho, en Python los bloques de código no están definidos, como en otros lenguajes con `{}` o bloques `BEGIN...END` sino por la indentación (que es obligatoria)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def formatea_precio(precio, simbolo):\n",
" precio_string = str(precio)\n",
" return precio_string + simbolo\n",
"\n",
"formatea_precio(2500, ' $')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Una función que devuelva un resultado necesita obligatoriamente terminar con una expresión `return`. Si se omite, devolverá `None`."
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"### Bucles\n",
"\n",
"Los bucles son similares a los de muchos otros lenguajes. Como en la definición de las funciones, el bloque de código que sigue a la expresión `for` está indentado."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for precio in precios:\n",
" print(formatea_precio(precio, ' $'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Las _list comprehensions_ evitan la necesidad de construir muchos bucles."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Usa un bucle que devuelva el mismo resultado que la siguiente compresión de lista:\n",
"\n",
"```\n",
"[f'{precio} €' for precio in precios]\n",
"```\n",
"\n",
"**Nota:** es importante que el resultado sea una lista con los elementos esperados. Es decir, no vale simplemente imprimir sus elementos con `print`. Repasa la sección de listas para ver cómo crear una vacía e ir añadiendo elementos."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Expresiones condicionales\n",
"\n",
"Como en casi todos los lenguajes de programación, se pueden usar expresiones condicionales con la consabida estructura\n",
"\n",
"```\n",
"if condicion:\n",
" ...\n",
"```\n",
"\n",
"o, alternativamente, \n",
"```\n",
"if condicion:\n",
" ...\n",
"elif:\n",
" ...\n",
"else:\n",
" ...\n",
"```\n",
"\n",
"De nuevo, los bloques de código que siguen tanto a la expresión `for` como a `else` tienen que estar indentados."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Construye una función que, dado un precio y un umbral, devuelva la cadena `caro` o `barato` según si el precio está por encima o por debajo del umbral. Crea entonces una columna adicional en `venta` usando como umbral la mediana del precio."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Usa la función del ejercicio anterior, bucles, etc. para crear una columna adicional en `venta` que indique si el precio de un piso es caro o barato según supere o no la mediana de precios de su distrito."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## El groupby más genérico\n",
"\n",
"Los ejemplos de agrupaciones que vimos en el notebook de pandas están pensados para los casos más típicos: sacar algunas estadísticas como medias, máximos, ... de una o varias columnas.\n",
"\n",
"Pero habitualmente necesitamos funciones más flexibles, que implican aplicar funciones propias a cada uno de los grupos. Para hacer esto, podemos utilizar `apply` y devolver una `Series`.\n",
"\n",
"Aunque, no debemos abusar de ellos. Si hay una alternativa a nuestro `apply` + función propia como las de arriba (`mean`, `max`, ...), serán mucho más eficientes (más rápidas!).\n",
"\n",
"Por ejemplo, vamos a devolver la diferencia relativa de precio entre el primer y último dato de cada distrito."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def calcula_diferencia_relativa_precio(grupo):\n",
" # Ordenamos cronológicamente\n",
" grupo = grupo.sort_values(['ano', 'quarter'])\n",
" \n",
" # Cogemos el primero (dato más antiguo) y el último (más reciente)\n",
" precio_mas_antiguo = grupo.precio.iloc[0]\n",
" precio_mas_reciente = grupo.precio.iloc[-1]\n",
"\n",
" # Queremos el incremento relativo\n",
" diferencia = (precio_mas_reciente - precio_mas_antiguo) / precio_mas_antiguo\n",
"\n",
" # Lo devolvemos como pd.Series\n",
" return pd.Series({'incremento_precio_relativo': diferencia})\n",
"\n",
"alquiler.groupby('distrito').apply(calcula_diferencia_relativa_precio).reset_index()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Con estas funciones personalizadas, también podemos devolver varias filas por cada grupo.\n",
"\n",
"Por ejemplo, vamos a devolver dos filas por grupo. Queremos saber, para cada distrito, cuándo y con qué valor se alcanzan los máximos y mínimos de precios."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def get_rent_min_max(grupo):\n",
" # Ordenamos por precio\n",
" grupo = grupo.sort_values('precio')\n",
" \n",
" # Cogemos el primero (mínimo) y el último (máximo)\n",
" minimo = grupo.iloc[0]\n",
" maximo = grupo.iloc[-1]\n",
"\n",
" # Devolvemos estas filas nuevas\n",
" nuevo_dataframe = pd.DataFrame({\n",
" 'tipo': ['minimo', 'maximo'],\n",
" 'ano': [minimo.ano, maximo.ano],\n",
" 'quarter': [minimo.quarter, maximo.quarter],\n",
" 'precio': [minimo.precio, maximo.precio],\n",
" })\n",
"\n",
" return nuevo_dataframe\n",
"\n",
"resultado = alquiler.groupby('distrito').apply(get_rent_min_max).reset_index()\n",
"\n",
"# al hacer esto, sale una columna \"fea\" resultado de incluir un dataframe dentro de otro\n",
"# podemos eliminarla con drop\n",
"resultado.drop(columns=[\"level_1\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "libropython",
"language": "python",
"name": "libropython"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: notebooks/04_apis.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Introducción a las APIs\n",
"\n",
"Las [APIs](https://en.wikipedia.org/wiki/Application_programming_interface) (Application Programming Interface) se utilizan de manera habitual para acceder a datos, servicios o facilitar la comunicación entre programas.\n",
"\n",
"Son muy útiles en los siguientes casos:\n",
"\n",
"* Los datos cambian rápidamente. Por ejemplo, predicciones metereológicas a corto plazo, o el estado actual de la bolsa. En estos casos, no tiene sentido tener un dataset estático que regenerar continuamente.\n",
"* Necesitamos una parte pequeña de un dataset mucho más grande. Por ejemplo, vamos a analizar los tweets de una determinada cuenta. Sería innecesariamente costo tener que descargarse la base de datos de Twitter completa para consultar un subconjunto muy pequeño.\n",
"* Para consumir un servicio especializado. Por ejemplo, la geocodificación inversa (consiste en un servicio al que le pasas una latitud y longitud y te devuelve la dirección, ciudad, país, ... en la que se encuentra. Para hacerlo por nosotros mismos, necesitaríamos una base geolocalizada global.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En realidad, su uso es muy parecido a la consulta de un sitio web desde un explorador. Tanto la API como el sitio web residen en un servidor web remoto (normalmente, accesible desde internet) y contestan a las peticiones que hacen los clientes. La gran diferencia reside, principalmente, en el formato de la respuesta:\n",
"\n",
"* Al consultar un sitio web, el resultado se devuelve para que sea interpretable por humanos. Suele estar formado por HTML que el explorador renderiza para nosotros.\n",
"* Al consultar una API, el resultado se devuelve en una forma estructurada para que sea interpretable por otro programa. El formato más habitual es el [JSON](https://en.wikipedia.org/wiki/JSON), aunque existen otros.\n",
"\n",
"Un ejemplo de JSON:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Peticiones a APIs\n",
"\n",
"Vamos a hacer una petición a una API. Para ello, necesitamos saber:\n",
"\n",
"* El endpoint (url)\n",
"* Si necesita parámetros, cuáles son, y dónde se colocan (en el query string o en el cuerpo de la petición)\n",
"\n",
"Un ejemplo de petición a una API que no necesita parámetros"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import requests"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = requests.get('https://api.exchangeratesapi.io/latest')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Código de estado: 200 indica OK\n",
"\n",
"response.status_code"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Cabeceras: dan información sobre el servidor, el formato de la respuesta, ...\n",
"\n",
"response.headers"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Los datos de respuesta, en JSON\n",
"\n",
"response_data = response.json()\n",
"response_data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La librería parsea el JSON de respuesta automáticamente a una lista de diccionarios (con las anidaciones correspondientes. P.e. podemos extraer campos concretos de esta forma:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response_data['base']"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response_data['rates']['AUD']"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response_data['rates']['USD']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Códigos de respuesta\n",
"\n",
"El _status code_ nos indica si ha ido bien o no la petición. Además, en caso de error, nos da información sobre la causa de este. Los más utilizados son:\n",
"\n",
"* 200: la petición ha ido bien\n",
"* 301: el servidor está redireccionando la petición a otro\n",
"* 401: error de autenticación\n",
"* 400: la petición es incorrecta (p.e. porque falta algún parámetro o están mal formados)\n",
"* 403: prohibido, no tienes permisos suficientes\n",
"* 404: el recurso consultado no existe\n",
"* 500: el servidor ha dado un error inesperado\n",
"\n",
"Puedes ver la lista completa [aquí](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Verbos\n",
"\n",
"Las peticiones a las APIs usan verbos. En el ejemplo de antes, hemos utilizado `GET`. Además, este verbo es el que se usa por defecto a través del explorador.\n",
"\n",
"Los más comunes y el uso que se les suele dar son:\n",
"\n",
"* GET: para consulta\n",
"* POST: para insertar un nuevo dato o disparar una acción\n",
"* PUT: para actualizar un registro\n",
"* DELETE: para eliminar un registro"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Parámetros\n",
"\n",
"Los parámetros que incluimos en la petición pueden ir de formas diversas:\n",
"\n",
"* Como parte de la URL. P.e. `https://api.exchangeratesapi.io/2010-04-01`\n",
"* Como parte del query string. La URL y su query string se separan con el símbolo `?`, y tienen la forma `clave=valor`. P.e. `https://api.exchangeratesapi.io/latest?base=USD`. Este es el lugar habitual de los parámetros en las peticiones `GET`.\n",
"* En el cuerpo de la petición. Es el lugar habitual de los parámetros en las peticiones `POST` y `PUT`.\n",
"\n",
"Un ejemplo de petición `POST` con parámetros en el cuerpo de la petición:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = requests.post('https://httpbin.org/post', data={'clave': 'valor'})\n",
"response.json()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Un ejemplo similar, pero enviando los parámetros en el cuerpo de la petición __en formato JSON__:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = requests.post('https://httpbin.org/post', json={'clave': 'valor'})\n",
"response.json()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"Investiga en la documentación de la librería [requests](http://www.python-requests.org/en/latest/) cómo pasar parámetros en el query string. También consulta la documentación de la API [exchangeratesapi](https://exchangeratesapi.io/).\n",
"\n",
"Con esta información, extrae el valor actual de la conversión de la libra esterlina `GBP` al resto de monedas.\n",
"\n",
"*Nota*: hazlo sin poner manualmente el query string en la URL"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"También sobre la API de `exchangeratesapi`, consulta el histórico disponible del valor entre dólares americanos y libras entre el 15 de agosto y 15 de septiembre de 2018"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"Consulta la documentación de estas dos APIs:\n",
"\n",
"* [Nominatim - reverse geocoding](http://wiki.openstreetmap.org/wiki/Nominatim#Reverse_Geocoding)\n",
"* [UK Police - crimes at location](https://data.police.uk/docs/method/crimes-at-location)\n",
"\n",
"Primero, consulta a la API de Nominatim para conocer cuál es la dirección asociada a estas coordenadas:\n",
"\n",
"* Latitud: 51.4965946\n",
"* Longitud: -0.1436476\n",
"\n",
"El resultado debe estar en formato JSON."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Luego, consulta a la API de la policía de UK los crímenes que se cometieron en esa localización en julio de 2018"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ejercicio swapi\n",
"\n",
"Haz una petición a la API de [swapi](https://swapi.co/documentation) con una búsqueda de todos los personajes que tengan el apellido `skywalker`. Examina la respuesta e imprime los nombres resultantes utilizando una `list comprehension` (no utilices bucles)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ejercicio libre\n",
"\n",
"Revisa [este listado](https://github.com/toddmotto/public-apis) de APIs públicas. Elige alguna que te llame la atención, consulta su documentación y extrae información que sea de tu interés."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Referencia\n",
"\n",
"* Documentación [requests](http://www.python-requests.org/en/latest/)\n",
"* Documentación [API citybik](https://api.citybik.es/v2/)\n",
"* Documentación [folium](http://python-visualization.github.io/folium/quickstart.html)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "libropython",
"language": "python",
"name": "libropython"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: notebooks/05_web_scraping.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Web scraping\n",
"\n",
"Cuando necesitamos extraer información publicada en internet, lo ideal es consultar una API, porque:\n",
"\n",
"* Las respuestas contienen información estructurada\n",
"* En general, el propio servicio nos da documentación sobre cómo hacer peticiones y qué tipo de información podemos solicitar\n",
"\n",
"Pero muchas veces nos encontramos con información en páginas web (en formato [HTML](https://es.wikipedia.org/wiki/HTML)) que nos gustaría obtener, pero sin API disponible.\n",
"\n",
"Estas páginas `HTML` tienen cierta estructura, aunque con ciertos contras:\n",
"\n",
"* Es más compleja, puede tener muchos niveles de anidamiento\n",
"* Es inestable. Están diseñadas para que se vean bien desde el explorador, no para guardar una estructura de consulta. De un día para otro, puede verse alterada por la incorporación de nuevos elementos visuales u otros motivos.\n",
"* Puede ser modificada por código cliente (javascript) en diferentes momentos: al cargar la página, al interaccionar con algún elemento, ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"Desde tu explorador, consulta el código fuente de una página de tu interés. Por ejemplo, para hacerlo en chrome:\n",
"\n",
"* Accede a la página, p.e. [esta](https://es.wikipedia.org/wiki/HTML).\n",
"* Haz click derecho y pulsa `View page source`. Otra opción es pulsar `Inspect`, que además abrirá las herramientas de desarrollador de Chrome, muy útiles para navegar por la estructura de la página."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Scraping de elementos html\n",
"\n",
"La librería que vamos a utilizar es [Beautiful Soup](https://pypi.org/project/beautifulsoup4/). Nos permite buscar elementos y navegar por la estructura del html fácilmente.\n",
"\n",
"Vemos dos ejemplos, uno sobre milanuncios y otro sobre spotahome, por si nos _banean_.\n",
"\n",
"### milanuncios\n",
"\n",
"Imaginemos que queremos comparar precios de un determinado modelo de motocicleta de segunda mano. P.e. con [esta búsqueda](https://www.milanuncios.com/motos-de-carretera/duke-390.htm) en milanuncios.\n",
"\n",
"La mayor parte de las webs con contenido interesante (que hacen negocio gracias a su contenido) intentan protegerlas para evitar que les hagan scraping. Hay varias formas de simular que nuestro script es humano en lugar de un bot, algunas más básicas y otras más complejas. Por ahora, vamos a sobrescribir nuestro _user agent_. Es una cabecera que va en las peticiones diciendo quiénes somos (p.e. qué tipo de explorador usamos). Por defecto, la librería `requests` que vamos a usar, avisa que somos un bot. Vamos a sobrescribir esta cabecera para _disimular_ un poco. Podemos copiar uno popular de (aquí)[https://developers.whatismybrowser.com/useragents/explore/software_type_specific/web-browser/]."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"from bs4 import BeautifulSoup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"headers = {\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ahora, nos descargamos el html con `requests`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"page = requests.get('https://www.milanuncios.com/motos-de-carretera/duke-390.htm', headers=headers)\n",
"page"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos ver el contenido examinando la propiedad `content`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"page.content"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este contenido es solo texto, no tiene estructura. Aún no podemos hacer búsquedas ni navegar por él.\n",
"\n",
"Para hacerlo, creamos una instancia de `Beautiful Soup` y lo parseamos"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"soup = BeautifulSoup(page.content, 'html.parser')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### SOS: ¡me han baneado!\n",
"\n",
"Si estamos haciendo esto en clase, es muy probable que a la mayoría, nos detecten como bots y nos baneen. Esto pasa porque somos 30 o 40 personas, haciendo la misma petición desde la misma IP y con el mismo user agent.\n",
"\n",
"Puedes usar el html offline que hay en `dat` para seguir con el ejercicio. Descomenta el siguiente código y sigue adelante"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Descomenta esto si te han baneado\n",
"# with open('dat/milanuncios.html') as f:\n",
"# soup = BeautifulSoup(f, 'html.parser')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sobre esto, podemos hacer búsquedas con `find` y `find_all` (o `select_one` y `select` si prefieres utilizar [selectores css](https://en.wikipedia.org/wiki/Cascading_Style_Sheets#Selector)). Sobre nuestro ejemplo, vamos a buscar todos los precios. Examinando el código fuente, vemos que son etiquetas `div` con clase `aditem-price`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"div_precios = soup.find_all('div', class_='aditem-price')\n",
"div_precios"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`find_all` devuelve una lista de elementos. Sobre ellos, podemos hacer:\n",
"\n",
"`children` para sacar el listado de todos los hijos."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(div_precios[0].children)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`get_text()` para sacar el texto de todos los hijos"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"div_precios[0].get_text()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Por tanto, para sacar el listado de todos los precios podemos hacer:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"[list(div_precio.children)[0] for div_precio in div_precios]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tienes más funciones útiles con pequeños ejemplos [aquí](http://akul.me/blog/2016/beautifulsoup-cheatsheet/)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"Crea un dataframe de pandas en el que cada fila sea un anuncio y tenga como columnas información que consideres relevante: precio, kilómetros, año, cilindrada, texto del anuncio, ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ejercicio\n",
"\n",
"Modifica el código anterior para que, además de bajarse la página actual, navegue por el resto de páginas e incorpore también esos anuncios a tu dataframe."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Scraping de tablas\n",
"\n",
"A menudo, la información que nos interesa descargar está en tablas y nuestro objetivo es importarlas en tablas de Pandas. Esta conversión suele exigir la manipulación del texto, números y fechas contenidas en la tabla original, lo que nos obligará a repasar cómo realizar esas operaciones y aplicarlas a filas y columnas de las tablas.\n",
"\n",
"La estructura que suelen tener la tablas en `html` es:\n",
"\n",
"```\n",
"<table>\n",
" <thead>\n",
" <tr>\n",
" <th>Columna A</th>\n",
" <th>Columna B</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td>A1</td>\n",
" <td>B1</td>\n",
" </tr>\n",
" <tr>\n",
" <td>A2</td>\n",
" <td>B2</td>\n",
" </tr>\n",
" </tbody>\n",
"</table> \n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Necesitaremos los siguientes módulos además de `requests` y `BeautifulSoup` importados anteriormente:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import re"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Primero, hacemos una petición para descargar la página de interés (que contiene las cotizaciones de las acciones del IBEX 35 en tiempo _casi_ real)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"base_url = \"https://www.eleconomista.es/indice/IBEX-35\"\n",
"res = requests.get(base_url)\n",
"contenido = res.content"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La siguiente línea procesa el HTML de la página que hemos descargado:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"soup = BeautifulSoup(contenido, \"html.parser\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Una vez procesado el HTML, es posible buscar elementos dentro de él. En particular, podemos buscar los elementos de tipo `table`, es decir, tablas."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tablas = soup.find_all('table')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"El objeto `tablas` contiene todas las tablas presentes en la página. Hay que tener cuidado con dichas tablas porque muchas páginas utilizan elementos de tipo `table` para estructurar el contenido. Por eso, en algunas páginas, aunque parezca haber una única tabla, puede haber otras con una información no interesante que toca descartar."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(tablas)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos extraer las filas de todas estas tablas"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"lineas = [x for tabla in tablas for x in tabla.find_all('tr')]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"para luego extraer los contenidos de cada fila individualmente haciendo"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"datos = [[x.text for x in linea.find_all('td')] for linea in lineas]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos inspeccionar parte del objeto resultante:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"datos[0:3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vemos que hay filas que contienen la información de interés junto con otras que contienen cabeceras y otra información irrelevante. En general, la situación puede ser más complicada y se hace necesario estudiar el objeto `tablas` para seleccionar la de interés.\n",
"\n",
"En nuestro caso, podemos filtrar las líneas menos relevantes así:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"datos = [x for x in datos if len(x) > 0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finalmente, podemos crear una tabla de Pandas:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"datos = pd.DataFrame(datos)\n",
"datos"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Usa los elementos `th` de la primera fila de las tablas para extraer nombres para las columnas de la tabla. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Elimina las columnas irrelevantes y cambia los nombres de las columnas por otros breves y sin caracteres extraños o que dificulten el posproceso."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Ejercicio\n",
"\n",
"Cambia el formato de las columnas adecuadamente: convierte a numéricas, etc., las columnas que lo requieran."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Riesgos del scraping\n",
"\n",
"El scraping es una técnica potente pero tiene varios contras:\n",
"\n",
"* Implica mayor tiempo de desarrollo y mayor esfuerzo en la limpieza de datos (en comparación con otras fuentes como APIs, BDs, ...)\n",
"* Si hay que scrapear gran cantidad de páginas, es lento\n",
"* Los servidores objetivo de nuestro scraping pueden tener técnicas para evitarlo. Por ejemplo, bloquear la IP temporalmente o introducir delays en las respuestas si hacemos muchas peticiones en poco tiempo. Esto pasa especialmente en las grandes webs recelosas de sus datos (p.e. linkedin, amazon, ...).\n",
"* El código de scraping escrito hoy puede no funcionar mañana, si la web destino cambia nombres, etiquetas o estructura. Si se sube a producción para lanzarlo periódicamente, hay que ser conscientes de que en algún momento fallará, y establecer mecanismos de alerta\n",
"\n",
"## Javascript\n",
"\n",
"Es posible que te encuentres con algún caso en el que no puedas descargar tal cual el html y parsearlo, principalmente por dos motivos:\n",
"\n",
"* La estructura de la página se genera parcial o totalmente en cliente\n",
"* Debemos interactuar con algún elemento para mostrar la información que queremos (p.e. completar un campo de búsuqeda, hacer click en algún botón, ...)\n",
"\n",
"En estos casos, hay que ejecutar en un navegador local el código javascript de la página destino. Para esta tarea, puedes utilizar [Selenium]().\n",
"\n",
"[Aquí](https://medium.freecodecamp.org/better-web-scraping-in-python-with-selenium-beautiful-soup-and-pandas-d6390592e251) un post con un ejemplo de uso."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "libropython",
"language": "python",
"name": "libropython"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: notebooks/06_intro_visualizacion.Rmd
================================================
---
title: "Visualización de datos"
author: "Luz Frias"
output:
revealjs::revealjs_presentation:
pandoc_args: [ "--slide-level", "1" ]
transition: none
background_transition: none
reveal_options:
controls: false
width: 1200
---
## Introducción visualización de datos
* Es parte del análisis exploratorio de los datos
* De un vistazo, podemos entender:
* Distribuciones
* Relaciones entre atributos
* ...
## Evitar la distorsión de los datos


## El contexto de los gráficos
* El gráfico debe servir para apoyar una serie de conclusiones / explicaciones a lo observado

## Un mismo gráfico para exponer varios niveles de detalle
* Combinando formas, colores y posición podemos describir varias dimensiones de la información de un solo vistazo

* La dimensión tiempo en los gráficos: [ejemplo](https://www.visualcapitalist.com/animation-the-worlds-10-largest-economies-by-gdp-1960-today/)
## Los colores
* Es importante escoger una escala que refleje la naturaleza de los datos:
* Secuencial: refleja progresivamente las diferencias entre poco y mucho. Por ejemplo, los ingresos mensuales medios por barrio.
* Divergente: tiene naturaleza secuencial, pero interesa destacar los extremos y el punto medio. Por ejemplo, el mapa de intención de voto demócrata vs republicano en EEUU.
* Cualitativa: refleja categóricas sin orden. Por ejemplo, hombres vs mujeres.
* Es muy complicado elegir bien una escala. Usa herramientas que ya las proporcionan, como [colorbrewer](http://colorbrewer2.org/)
## La escalas
* Naturaleza de la comparación: normal, logarítmica, ... [Ejemplo](https://www.researchtrends.com/wp-content/uploads/2012/09/KL2-log-scale.jpg)
* El cero: ¿comparaciones absolutas o relativas? [Ejemplo](http://www.datapine.com/blog/wp-content/uploads/2014/06/Same-Data-Different-Y-Axis-Data-Visualization-Designed-to-Mislead.png)
* Varias escalas: deben usarse bien para evitar confusiones. [Ejemplo](https://www.elsevier.com/__data/assets/image/0015/35043/Figure-2.jpg)
## La importancia del tipo de gráfico
* [Chart suggestion](https://apandre.files.wordpress.com/2011/02/chartchooserincolor.jpg)
## Ejemplos buenos gráficos: ¿por qué?
* [Servicio militar EEUU](http://www.npr.org/2011/07/03/137536111/by-the-numbers-todays-military)
* [Cartograma elecciones UK](http://www.viewsoftheworld.net/wp-content/uploads/2015/05/ukelection2015_mapviews.jpg)
* [Interactivo rutinas](https://podio.com/site/creative-routines)
* [Animación envejecimiento población](https://www.visualcapitalist.com/us-population-pyramid-1980-2050/)
* [Tendencias twitter](http://echeloninsights.com/wp-content/uploads/2014/12/theyearinnews20141.png)
* [Pictoline](https://twitter.com/pictoline/status/796541916397576194?lang=en)
* [Elecciones catalanas](http://www.huffingtonpost.es/2015/09/28/elecciones-catalanas-twitter_n_8206438.html)
* [Redes sociales y grafos](https://twitter.com/graphext/status/748119214431543296)
## Ejemplos malos gráficos: ¿qué cambiarías?
* [Categorías empresas](https://www.datanalytics.com/2011/11/08/bump-charts-para-comparar-graficamente-proporciones-entre-periodos/)
* [IEH](https://www.datanalytics.com/2015/04/23/valores-diferentes/)
* [Ejes](https://www.datanalytics.com/2013/09/05/donde-deberian-comenzar-los-ejes/)
* [Pomelos](https://www.datanalytics.com/2013/08/27/el-pomelazo-del-csic/)
* [Proporciones](https://www.datanalytics.com/2013/03/19/mapas-realmente-necesarios/)
* [El engaño](https://www.datanalytics.com/2011/01/31/un-grafico-enganabobos/)
* [Blog dedicado a los gráficos sin sentido](http://viz.wtf/)
================================================
FILE: notebooks/06_intro_visualizacion.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="author" content="Luz Frias" />
<title>Visualización de datos</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
<style type="text/css">
html, body, .reveal div, .reveal span, .reveal applet, .reveal object, .reveal iframe,
.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal p, .reveal blockquote, .reveal pre,
.reveal a, .reveal abbr, .reveal acronym, .reveal address, .reveal big, .reveal cite, .reveal code,
.reveal del, .reveal dfn, .reveal em, .reveal img, .reveal ins, .reveal kbd, .reveal q, .reveal s, .reveal samp,
.reveal small, .reveal strike, .reveal strong, .reveal sub, .reveal sup, .reveal tt, .reveal var,
.reveal b, .reveal u, .reveal center,
.reveal dl, .reveal dt, .reveal dd, .reveal ol, .reveal ul, .reveal li,
.reveal fieldset, .reveal form, .reveal label, .reveal legend,
.reveal table, .reveal caption, .reveal tbody, .reveal tfoot, .reveal thead, .reveal tr, .reveal th, .reveal td,
.reveal article, .reveal aside, .reveal canvas, .reveal details, .reveal embed,
.reveal figure, .reveal figcaption, .reveal footer, .reveal header, .reveal hgroup,
.reveal menu, .reveal nav, .reveal output, .reveal ruby, .reveal section, .reveal summary,
.reveal time, .reveal mark, .reveal audio, .reveal video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline; }
.reveal article, .reveal aside, .reveal details, .reveal figcaption, .reveal figure,
.reveal footer, .reveal header, .reveal hgroup, .reveal menu, .reveal nav, .reveal section {
display: block; }
html,
body {
width: 100%;
height: 100%;
overflow: hidden; }
body {
position: relative;
line-height: 1;
background-color: #fff;
color: #000; }
.reveal .slides section .fragment {
opacity: 0;
visibility: hidden;
-webkit-transition: all .2s ease;
transition: all .2s ease; }
.reveal .slides section .fragment.visible {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.grow {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.grow.visible {
-webkit-transform: scale(1.3);
transform: scale(1.3); }
.reveal .slides section .fragment.shrink {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.shrink.visible {
-webkit-transform: scale(0.7);
transform: scale(0.7); }
.reveal .slides section .fragment.zoom-in {
-webkit-transform: scale(0.1);
transform: scale(0.1); }
.reveal .slides section .fragment.zoom-in.visible {
-webkit-transform: none;
transform: none; }
.reveal .slides section .fragment.fade-out {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.fade-out.visible {
opacity: 0;
visibility: hidden; }
.reveal .slides section .fragment.semi-fade-out {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.semi-fade-out.visible {
opacity: 0.5;
visibility: visible; }
.reveal .slides section .fragment.strike {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.strike.visible {
text-decoration: line-through; }
.reveal .slides section .fragment.fade-up {
-webkit-transform: translate(0, 20%);
transform: translate(0, 20%); }
.reveal .slides section .fragment.fade-up.visible {
-webkit-transform: translate(0, 0);
transform: translate(0, 0); }
.reveal .slides section .fragment.fade-down {
-webkit-transform: translate(0, -20%);
transform: translate(0, -20%); }
.reveal .slides section .fragment.fade-down.visible {
-webkit-transform: translate(0, 0);
transform: translate(0, 0); }
.reveal .slides section .fragment.fade-right {
-webkit-transform: translate(-20%, 0);
transform: translate(-20%, 0); }
.reveal .slides section .fragment.fade-right.visible {
-webkit-transform: translate(0, 0);
transform: translate(0, 0); }
.reveal .slides section .fragment.fade-left {
-webkit-transform: translate(20%, 0);
transform: translate(20%, 0); }
.reveal .slides section .fragment.fade-left.visible {
-webkit-transform: translate(0, 0);
transform: translate(0, 0); }
.reveal .slides section .fragment.current-visible {
opacity: 0;
visibility: hidden; }
.reveal .slides section .fragment.current-visible.current-fragment {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.highlight-red,
.reveal .slides section .fragment.highlight-current-red,
.reveal .slides section .fragment.highlight-green,
.reveal .slides section .fragment.highlight-current-green,
.reveal .slides section .fragment.highlight-blue,
.reveal .slides section .fragment.highlight-current-blue {
opacity: 1;
visibility: visible; }
.reveal .slides section .fragment.highlight-red.visible {
color: #ff2c2d; }
.reveal .slides section .fragment.highlight-green.visible {
color: #17ff2e; }
.reveal .slides section .fragment.highlight-blue.visible {
color: #1b91ff; }
.reveal .slides section .fragment.highlight-current-red.current-fragment {
color: #ff2c2d; }
.reveal .slides section .fragment.highlight-current-green.current-fragment {
color: #17ff2e; }
.reveal .slides section .fragment.highlight-current-blue.current-fragment {
color: #1b91ff; }
.reveal:after {
content: '';
font-style: italic; }
.reveal iframe {
z-index: 1; }
.reveal a {
position: relative; }
.reveal .stretch {
max-width: none;
max-height: none; }
.reveal pre.stretch code {
height: 100%;
max-height: 100%;
box-sizing: border-box; }
.reveal .controls {
display: none;
position: fixed;
width: 110px;
height: 110px;
z-index: 30;
right: 10px;
bottom: 10px;
-webkit-user-select: none; }
.reveal .controls button {
padding: 0;
position: absolute;
opacity: 0.05;
width: 0;
height: 0;
background-color: transparent;
border: 12px solid transparent;
-webkit-transform: scale(0.9999);
transform: scale(0.9999);
-webkit-transition: all 0.2s ease;
transition: all 0.2s ease;
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent; }
.reveal .controls .enabled {
opacity: 0.7;
cursor: pointer; }
.reveal .controls .enabled:active {
margin-top: 1px; }
.reveal .controls .navigate-left {
top: 42px;
border-right-width: 22px;
border-right-color: #000; }
.reveal .controls .navigate-left.fragmented {
opacity: 0.3; }
.reveal .controls .navigate-right {
left: 74px;
top: 42px;
border-left-width: 22px;
border-left-color: #000; }
.reveal .controls .navigate-right.fragmented {
opacity: 0.3; }
.reveal .controls .navigate-up {
left: 42px;
border-bottom-width: 22px;
border-bottom-color: #000; }
.reveal .controls .navigate-up.fragmented {
opacity: 0.3; }
.reveal .controls .navigate-down {
left: 42px;
top: 74px;
border-top-width: 22px;
border-top-color: #000; }
.reveal .controls .navigate-down.fragmented {
opacity: 0.3; }
.reveal .progress {
position: fixed;
display: none;
height: 3px;
width: 100%;
bottom: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, 0.2); }
.reveal .progress:after {
content: '';
display: block;
position: absolute;
height: 20px;
width: 100%;
top: -20px; }
.reveal .progress span {
display: block;
height: 100%;
width: 0px;
background-color: #000;
-webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
.reveal .slide-number {
position: fixed;
display: block;
right: 8px;
bottom: 8px;
z-index: 31;
font-family: Helvetica, sans-serif;
font-size: 12px;
line-height: 1;
color: #fff;
background-color: rgba(0, 0, 0, 0.4);
padding: 5px; }
.reveal .slide-number-delimiter {
margin: 0 3px; }
.reveal {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
-ms-touch-action: none;
touch-action: none; }
.reveal .slides {
position: absolute;
width: 100%;
height: 100%;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
overflow: visible;
z-index: 1;
text-align: center;
-webkit-perspective: 600px;
perspective: 600px;
-webkit-perspective-origin: 50% 40%;
perspective-origin: 50% 40%; }
.reveal .slides > section {
-ms-perspective: 600px; }
.reveal .slides > section,
.reveal .slides > section > section {
display: none;
position: absolute;
width: 100%;
padding: 20px 0px;
z-index: 10;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-transition: -webkit-transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), -webkit-transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
transition: transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
.reveal[data-transition-speed="fast"] .slides section {
-webkit-transition-duration: 400ms;
transition-duration: 400ms; }
.reveal[data-transition-speed="slow"] .slides section {
-webkit-transition-duration: 1200ms;
transition-duration: 1200ms; }
.reveal .slides section[data-transition-speed="fast"] {
-webkit-transition-duration: 400ms;
transition-duration: 400ms; }
.reveal .slides section[data-transition-speed="slow"] {
-webkit-transition-duration: 1200ms;
transition-duration: 1200ms; }
.reveal .slides > section.stack {
padding-top: 0;
padding-bottom: 0; }
.reveal .slides > section.present,
.reveal .slides > section > section.present {
display: block;
z-index: 11;
opacity: 1; }
.reveal.center,
.reveal.center .slides,
.reveal.center .slides section {
min-height: 0 !important; }
.reveal .slides > section.future,
.reveal .slides > section > section.future,
.reveal .slides > section.past,
.reveal .slides > section > section.past {
pointer-events: none; }
.reveal.overview .slides > section,
.reveal.overview .slides > section > section {
pointer-events: auto; }
.reveal .slides > section.past,
.reveal .slides > section.future,
.reveal .slides > section > section.past,
.reveal .slides > section > section.future {
opacity: 0; }
.reveal.slide section {
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
.reveal .slides > section[data-transition=slide].past,
.reveal .slides > section[data-transition~=slide-out].past,
.reveal.slide .slides > section:not([data-transition]).past {
-webkit-transform: translate(-150%, 0);
transform: translate(-150%, 0); }
.reveal .slides > section[data-transition=slide].future,
.reveal .slides > section[data-transition~=slide-in].future,
.reveal.slide .slides > section:not([data-transition]).future {
-webkit-transform: translate(150%, 0);
transform: translate(150%, 0); }
.reveal .slides > section > section[data-transition=slide].past,
.reveal .slides > section > section[data-transition~=slide-out].past,
.reveal.slide .slides > section > section:not([data-transition]).past {
-webkit-transform: translate(0, -150%);
transform: translate(0, -150%); }
.reveal .slides > section > section[data-transition=slide].future,
.reveal .slides > section > section[data-transition~=slide-in].future,
.reveal.slide .slides > section > section:not([data-transition]).future {
-webkit-transform: translate(0, 150%);
transform: translate(0, 150%); }
.reveal.linear section {
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
.reveal .slides > section[data-transition=linear].past,
.reveal .slides > section[data-transition~=linear-out].past,
.reveal.linear .slides > section:not([data-transition]).past {
-webkit-transform: translate(-150%, 0);
transform: translate(-150%, 0); }
.reveal .slides > section[data-transition=linear].future,
.reveal .slides > section[data-transition~=linear-in].future,
.reveal.linear .slides > section:not([data-transition]).future {
-webkit-transform: translate(150%, 0);
transform: translate(150%, 0); }
.reveal .slides > section > section[data-transition=linear].past,
.reveal .slides > section > section[data-transition~=linear-out].past,
.reveal.linear .slides > section > section:not([data-transition]).past {
-webkit-transform: translate(0, -150%);
transform: translate(0, -150%); }
.reveal .slides > section > section[data-transition=linear].future,
.reveal .slides > section > section[data-transition~=linear-in].future,
.reveal.linear .slides > section > section:not([data-transition]).future {
-webkit-transform: translate(0, 150%);
transform: translate(0, 150%); }
.reveal .slides > section[data-transition=default].past,
.reveal .slides > section[data-transition~=default-out].past,
.reveal.default .slides > section:not([data-transition]).past {
-webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
.reveal .slides > section[data-transition=default].future,
.reveal .slides > section[data-transition~=default-in].future,
.reveal.default .slides > section:not([data-transition]).future {
-webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
.reveal .slides > section > section[data-transition=default].past,
.reveal .slides > section > section[data-transition~=default-out].past,
.reveal.default .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
.reveal .slides > section > section[data-transition=default].future,
.reveal .slides > section > section[data-transition~=default-in].future,
.reveal.default .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
.reveal .slides > section[data-transition=convex].past,
.reveal .slides > section[data-transition~=convex-out].past,
.reveal.convex .slides > section:not([data-transition]).past {
-webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
.reveal .slides > section[data-transition=convex].future,
.reveal .slides > section[data-transition~=convex-in].future,
.reveal.convex .slides > section:not([data-transition]).future {
-webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
.reveal .slides > section > section[data-transition=convex].past,
.reveal .slides > section > section[data-transition~=convex-out].past,
.reveal.convex .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
.reveal .slides > section > section[data-transition=convex].future,
.reveal .slides > section > section[data-transition~=convex-in].future,
.reveal.convex .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
.reveal .slides > section[data-transition=concave].past,
.reveal .slides > section[data-transition~=concave-out].past,
.reveal.concave .slides > section:not([data-transition]).past {
-webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
.reveal .slides > section[data-transition=concave].future,
.reveal .slides > section[data-transition~=concave-in].future,
.reveal.concave .slides > section:not([data-transition]).future {
-webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
.reveal .slides > section > section[data-transition=concave].past,
.reveal .slides > section > section[data-transition~=concave-out].past,
.reveal.concave .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0); }
.reveal .slides > section > section[data-transition=concave].future,
.reveal .slides > section > section[data-transition~=concave-in].future,
.reveal.concave .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0); }
.reveal .slides section[data-transition=zoom],
.reveal.zoom .slides section:not([data-transition]) {
-webkit-transition-timing-function: ease;
transition-timing-function: ease; }
.reveal .slides > section[data-transition=zoom].past,
.reveal .slides > section[data-transition~=zoom-out].past,
.reveal.zoom .slides > section:not([data-transition]).past {
visibility: hidden;
-webkit-transform: scale(16);
transform: scale(16); }
.reveal .slides > section[data-transition=zoom].future,
.reveal .slides > section[data-transition~=zoom-in].future,
.reveal.zoom .slides > section:not([data-transition]).future {
visibility: hidden;
-webkit-transform: scale(0.2);
transform: scale(0.2); }
.reveal .slides > section > section[data-transition=zoom].past,
.reveal .slides > section > section[data-transition~=zoom-out].past,
.reveal.zoom .slides > section > section:not([data-transition]).past {
-webkit-transform: translate(0, -150%);
transform: translate(0, -150%); }
.reveal .slides > section > section[data-transition=zoom].future,
.reveal .slides > section > section[data-transition~=zoom-in].future,
.reveal.zoom .slides > section > section:not([data-transition]).future {
-webkit-transform: translate(0, 150%);
transform: translate(0, 150%); }
.reveal.cube .slides {
-webkit-perspective: 1300px;
perspective: 1300px; }
.reveal.cube .slides section {
padding: 30px;
min-height: 700px;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
box-sizing: border-box; }
.reveal.center.cube .slides section {
min-height: 0; }
.reveal.cube .slides section:not(.stack):before {
content: '';
position: absolute;
display: block;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
-webkit-transform: translateZ(-20px);
transform: translateZ(-20px); }
.reveal.cube .slides section:not(.stack):after {
content: '';
position: absolute;
display: block;
width: 90%;
height: 30px;
left: 5%;
bottom: 0;
background: none;
z-index: 1;
border-radius: 4px;
box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
-webkit-transform: translateZ(-90px) rotateX(65deg);
transform: translateZ(-90px) rotateX(65deg); }
.reveal.cube .slides > section.stack {
padding: 0;
background: none; }
.reveal.cube .slides > section.past {
-webkit-transform-origin: 100% 0%;
transform-origin: 100% 0%;
-webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
transform: translate3d(-100%, 0, 0) rotateY(-90deg); }
.reveal.cube .slides > section.future {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
transform: translate3d(100%, 0, 0) rotateY(90deg); }
.reveal.cube .slides > section > section.past {
-webkit-transform-origin: 0% 100%;
transform-origin: 0% 100%;
-webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
transform: translate3d(0, -100%, 0) rotateX(90deg); }
.reveal.cube .slides > section > section.future {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
transform: translate3d(0, 100%, 0) rotateX(-90deg); }
.reveal.page .slides {
-webkit-perspective-origin: 0% 50%;
perspective-origin: 0% 50%;
-webkit-perspective: 3000px;
perspective: 3000px; }
.reveal.page .slides section {
padding: 30px;
min-height: 700px;
box-sizing: border-box; }
.reveal.page .slides section.past {
z-index: 12; }
.reveal.page .slides section:not(.stack):before {
content: '';
position: absolute;
display: block;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.1);
-webkit-transform: translateZ(-20px);
transform: translateZ(-20px); }
.reveal.page .slides section:not(.stack):after {
content: '';
position: absolute;
display: block;
width: 90%;
height: 30px;
left: 5%;
bottom: 0;
background: none;
z-index: 1;
border-radius: 4px;
box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
-webkit-transform: translateZ(-90px) rotateX(65deg); }
.reveal.page .slides > section.stack {
padding: 0;
background: none; }
.reveal.page .slides > section.past {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
transform: translate3d(-40%, 0, 0) rotateY(-80deg); }
.reveal.page .slides > section.future {
-webkit-transform-origin: 100% 0%;
transform-origin: 100% 0%;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
.reveal.page .slides > section > section.past {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
transform: translate3d(0, -40%, 0) rotateX(80deg); }
.reveal.page .slides > section > section.future {
-webkit-transform-origin: 0% 100%;
transform-origin: 0% 100%;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
.reveal .slides section[data-transition=fade],
.reveal.fade .slides section:not([data-transition]),
.reveal.fade .slides > section > section:not([data-transition]) {
-webkit-transform: none;
transform: none;
-webkit-transition: opacity 0.5s;
transition: opacity 0.5s; }
.reveal.fade.overview .slides section,
.reveal.fade.overview .slides > section > section {
-webkit-transition: none;
transition: none; }
.reveal .slides section[data-transition=none],
.reveal.none .slides section:not([data-transition]) {
-webkit-transform: none;
transform: none;
-webkit-transition: none;
transition: none; }
.reveal .pause-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: black;
visibility: hidden;
opacity: 0;
z-index: 100;
-webkit-transition: all 1s ease;
transition: all 1s ease; }
.reveal.paused .pause-overlay {
visibility: visible;
opacity: 1; }
.no-transforms {
overflow-y: auto; }
.no-transforms .reveal .slides {
position: relative;
width: 80%;
height: auto !important;
top: 0;
left: 50%;
margin: 0;
text-align: center; }
.no-transforms .reveal .controls,
.no-transforms .reveal .progress {
display: none !important; }
.no-transforms .reveal .slides section {
display: block !important;
opacity: 1 !important;
position: relative !important;
height: auto;
min-height: 0;
top: 0;
left: -50%;
margin: 70px 0;
-webkit-transform: none;
transform: none; }
.no-transforms .reveal .slides section section {
left: 0; }
.reveal .no-transition,
.reveal .no-transition * {
-webkit-transition: none !important;
transition: none !important; }
.reveal .backgrounds {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
-webkit-perspective: 600px;
perspective: 600px; }
.reveal .slide-background {
display: none;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
visibility: hidden;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
-webkit-transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
.reveal .slide-background.stack {
display: block; }
.reveal .slide-background.present {
opacity: 1;
visibility: visible; }
.print-pdf .reveal .slide-background {
opacity: 1 !important;
visibility: visible !important; }
.reveal .slide-background video {
position: absolute;
width: 100%;
height: 100%;
max-width: none;
max-height: none;
top: 0;
left: 0; }
.reveal[data-background-transition=none] > .backgrounds .slide-background,
.reveal > .backgrounds .slide-background[data-background-transition=none] {
-webkit-transition: none;
transition: none; }
.reveal[data-background-transition=slide] > .backgrounds .slide-background,
.reveal > .backgrounds .slide-background[data-background-transition=slide] {
opacity: 1;
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
.reveal[data-background-transition=slide] > .backgrounds .slide-background.past,
.reveal > .backgrounds .slide-background.past[data-background-transition=slide] {
-webkit-transform: translate(-100%, 0);
transform: translate(-100%, 0); }
.reveal[data-background-transition=slide] > .backgrounds .slide-background.future,
.reveal > .backgrounds .slide-background.future[data-background-transition=slide] {
-webkit-transform: translate(100%, 0);
transform: translate(100%, 0); }
.reveal[data-background-transition=slide] > .backgrounds .slide-background > .slide-background.past,
.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=slide] {
-webkit-transform: translate(0, -100%);
transform: translate(0, -100%); }
.reveal[data-background-transition=slide] > .backgrounds .slide-background > .slide-background.future,
.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=slide] {
-webkit-transform: translate(0, 100%);
transform: translate(0, 100%); }
.reveal[data-background-transition=convex] > .backgrounds .slide-background.past,
.reveal > .backgrounds .slide-background.past[data-background-transition=convex] {
opacity: 0;
-webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
.reveal[data-background-transition=convex] > .backgrounds .slide-background.future,
.reveal > .backgrounds .slide-background.future[data-background-transition=convex] {
opacity: 0;
-webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
.reveal[data-background-transition=convex] > .backgrounds .slide-background > .slide-background.past,
.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=convex] {
opacity: 0;
-webkit-transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0); }
.reveal[data-background-transition=convex] > .backgrounds .slide-background > .slide-background.future,
.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=convex] {
opacity: 0;
-webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0); }
.reveal[data-background-transition=concave] > .backgrounds .slide-background.past,
.reveal > .backgrounds .slide-background.past[data-background-transition=concave] {
opacity: 0;
-webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
.reveal[data-background-transition=concave] > .backgrounds .slide-background.future,
.reveal > .backgrounds .slide-background.future[data-background-transition=concave] {
opacity: 0;
-webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
.reveal[data-background-transition=concave] > .backgrounds .slide-background > .slide-background.past,
.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=concave] {
opacity: 0;
-webkit-transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0); }
.reveal[data-background-transition=concave] > .backgrounds .slide-background > .slide-background.future,
.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=concave] {
opacity: 0;
-webkit-transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0); }
.reveal[data-background-transition=zoom] > .backgrounds .slide-background,
.reveal > .backgrounds .slide-background[data-background-transition=zoom] {
-webkit-transition-timing-function: ease;
transition-timing-function: ease; }
.reveal[data-background-transition=zoom] > .backgrounds .slide-background.past,
.reveal > .backgrounds .slide-background.past[data-background-transition=zoom] {
opacity: 0;
visibility: hidden;
-webkit-transform: scale(16);
transform: scale(16); }
.reveal[data-background-transition=zoom] > .backgrounds .slide-background.future,
.reveal > .backgrounds .slide-background.future[data-background-transition=zoom] {
opacity: 0;
visibility: hidden;
-webkit-transform: scale(0.2);
transform: scale(0.2); }
.reveal[data-background-transition=zoom] > .backgrounds .slide-background > .slide-background.past,
.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=zoom] {
opacity: 0;
visibility: hidden;
-webkit-transform: scale(16);
transform: scale(16); }
.reveal[data-background-transition=zoom] > .backgrounds .slide-background > .slide-background.future,
.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=zoom] {
opacity: 0;
visibility: hidden;
-webkit-transform: scale(0.2);
transform: scale(0.2); }
.reveal[data-transition-speed="fast"] > .backgrounds .slide-background {
-webkit-transition-duration: 400ms;
transition-duration: 400ms; }
.reveal[data-transition-speed="slow"] > .backgrounds .slide-background {
-webkit-transition-duration: 1200ms;
transition-duration: 1200ms; }
.reveal.overview {
-webkit-perspective-origin: 50% 50%;
perspective-origin: 50% 50%;
-webkit-perspective: 700px;
perspective: 700px; }
.reveal.overview .slides section {
height: 100%;
top: 0 !important;
opacity: 1 !important;
overflow: hidden;
visibility: visible !important;
cursor: pointer;
box-sizing: border-box; }
.reveal.overview .slides section:hover,
.reveal.overview .slides section.present {
outline: 10px solid rgba(150, 150, 150, 0.4);
outline-offset: 10px; }
.reveal.overview .slides section .fragment {
opacity: 1;
-webkit-transition: none;
transition: none; }
.reveal.overview .slides section:after,
.reveal.overview .slides section:before {
display: none !important; }
.reveal.overview .slides > section.stack {
padding: 0;
top: 0 !important;
background: none;
outline: none;
overflow: visible; }
.reveal.overview .backgrounds {
-webkit-perspective: inherit;
perspective: inherit; }
.reveal.overview .backgrounds .slide-background {
opacity: 1;
visibility: visible;
outline: 10px solid rgba(150, 150, 150, 0.1);
outline-offset: 10px; }
.reveal.overview .slides section,
.reveal.overview-deactivating .slides section {
-webkit-transition: none;
transition: none; }
.reveal.overview .backgrounds .slide-background,
.reveal.overview-deactivating .backgrounds .slide-background {
-webkit-transition: none;
transition: none; }
.reveal.overview-animated .slides {
-webkit-transition: -webkit-transform 0.4s ease;
transition: transform 0.4s ease; }
.reveal.rtl .slides,
.reveal.rtl .slides h1,
.reveal.rtl .slides h2,
.reveal.rtl .slides h3,
.reveal.rtl .slides h4,
.reveal.rtl .slides h5,
.reveal.rtl .slides h6 {
direction: rtl;
font-family: sans-serif; }
.reveal.rtl pre,
.reveal.rtl code {
direction: ltr; }
.reveal.rtl ol,
.reveal.rtl ul {
text-align: right; }
.reveal.rtl .progress span {
float: right; }
.reveal.has-parallax-background .backgrounds {
-webkit-transition: all 0.8s ease;
transition: all 0.8s ease; }
.reveal.has-parallax-background[data-transition-speed="fast"] .backgrounds {
-webkit-transition-duration: 400ms;
transition-duration: 400ms; }
.reveal.has-parallax-background[data-transition-speed="slow"] .backgrounds {
-webkit-transition-duration: 1200ms;
transition-duration: 1200ms; }
.reveal .overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
background: rgba(0, 0, 0, 0.9);
opacity: 0;
visibility: hidden;
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease; }
.reveal .overlay.visible {
opacity: 1;
visibility: visible; }
.reveal .overlay .spinner {
position: absolute;
display: block;
top: 50%;
left: 50%;
width: 32px;
height: 32px;
margin: -16px 0 0 -16px;
z-index: 10;
background-image: url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);
visibility: visible;
opacity: 0.6;
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease; }
.reveal .overlay header {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 40px;
z-index: 2;
border-bottom: 1px solid #222; }
.reveal .overlay header a {
display: inline-block;
width: 40px;
height: 40px;
padding: 0 10px;
float: right;
opacity: 0.6;
box-sizing: border-box; }
.reveal .overlay header a:hover {
opacity: 1; }
.reveal .overlay header a .icon {
display: inline-block;
width: 20px;
height: 20px;
background-position: 50% 50%;
background-size: 100%;
background-repeat: no-repeat; }
.reveal .overlay header a.close .icon {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC); }
.reveal .overlay header a.external .icon {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==); }
.reveal .overlay .viewport {
position: absolute;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
top: 40px;
right: 0;
bottom: 0;
left: 0; }
.reveal .overlay.overlay-preview .viewport iframe {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
border: 0;
opacity: 0;
visibility: hidden;
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease; }
.reveal .overlay.overlay-preview.loaded .viewport iframe {
opacity: 1;
visibility: visible; }
.reveal .overlay.overlay-preview.loaded .spinner {
opacity: 0;
visibility: hidden;
-webkit-transform: scale(0.2);
transform: scale(0.2); }
.reveal .overlay.overlay-help .viewport {
overflow: auto;
color: #fff; }
.reveal .overlay.overlay-help .viewport .viewport-inner {
width: 600px;
margin: auto;
padding: 20px 20px 80px 20px;
text-align: center;
letter-spacing: normal; }
.reveal .overlay.overlay-help .viewport .viewport-inner .title {
font-size: 20px; }
.reveal .overlay.overlay-help .viewport .viewport-inner table {
border: 1px solid #fff;
border-collapse: collapse;
font-size: 16px; }
.reveal .overlay.overlay-help .viewport .viewport-inner table th,
.reveal .overlay.overlay-help .viewport .viewport-inner table td {
width: 200px;
padding: 14px;
border: 1px solid #fff;
vertical-align: middle; }
.reveal .overlay.overlay-help .viewport .viewport-inner table th {
padding-top: 20px;
padding-bottom: 20px; }
.reveal .playback {
position: fixed;
left: 15px;
bottom: 20px;
z-index: 30;
cursor: pointer;
-webkit-transition: all 400ms ease;
transition: all 400ms ease; }
.reveal.overview .playback {
opacity: 0;
visibility: hidden; }
.reveal .roll {
display: inline-block;
line-height: 1.2;
overflow: hidden;
vertical-align: top;
-webkit-perspective: 400px;
perspective: 400px;
-webkit-perspective-origin: 50% 50%;
perspective-origin: 50% 50%; }
.reveal .roll:hover {
background: none;
text-shadow: none; }
.reveal .roll span {
display: block;
position: relative;
padding: 0 2px;
pointer-events: none;
-webkit-transition: all 400ms ease;
transition: all 400ms ease;
-webkit-transform-origin: 50% 0%;
transform-origin: 50% 0%;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
.reveal .roll:hover span {
background: rgba(0, 0, 0, 0.5);
-webkit-transform: translate3d(0px, 0px, -45px) rotateX(90deg);
transform: translate3d(0px, 0px, -45px) rotateX(90deg); }
.reveal .roll span:after {
content: attr(data-title);
display: block;
position: absolute;
left: 0;
top: 0;
padding: 0 2px;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform-origin: 50% 0%;
transform-origin: 50% 0%;
-webkit-transform: translate3d(0px, 110%, 0px) rotateX(-90deg);
transform: translate3d(0px, 110%, 0px) rotateX(-90deg); }
.reveal aside.notes {
display: none; }
.reveal .speaker-notes {
display: none;
position: absolute;
width: 70%;
max-height: 15%;
left: 15%;
bottom: 26px;
padding: 10px;
z-index: 1;
font-size: 18px;
line-height: 1.4;
color: #fff;
background-color: rgba(0, 0, 0, 0.5);
overflow: auto;
box-sizing: border-box;
text-align: left;
font-family: Helvetica, sans-serif;
-webkit-overflow-scrolling: touch; }
.reveal .speaker-notes.visible:not(:empty) {
display: block; }
@media screen and (max-width: 1024px) {
.reveal .speaker-notes {
font-size: 14px; } }
@media screen and (max-width: 600px) {
.reveal .speaker-notes {
width: 90%;
left: 5%; } }
.zoomed .reveal *,
.zoomed .reveal *:before,
.zoomed .reveal *:after {
-webkit-backface-visibility: visible !important;
backface-visibility: visible !important; }
.zoomed .reveal .progress,
.zoomed .reveal .controls {
opacity: 0; }
.zoomed .reveal .roll span {
background: none; }
.zoomed .reveal .roll span:after {
visibility: hidden; }
</style>
<style type="text/css">
@font-face {
font-family: 'News Cycle';
font-style: normal;
font-weight: 400;
src: url(data:application/x-font-truetype;base64,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AC3/aQAKAET/4gAKAEb/2QAKAEf/ugAKAEj/2QAKAEr/1wAKAFL/2QAKAFT/2QAKAFb/7AAKAG7/kQAKAIH/wwAKAIL/wwAKAIP/wwAKAIT/wwAKAIX/wwAKAIb/wwAKAIf/wwAKAKH/4gAKAKL/4gAKAKP/4gAKAKT/4gAKAKX/4gAKAKb/4gAKAKf/4gAKAKj/2QAKAKn/2QAKAKr/2QAKAKv/2QAKAKz/2QAKAK0AmgAKALAAGAAKALH/2QAKALP/2QAKALT/2QAKALX/2QAKALb/2QAKALf/2QAKALn/2QAKAMP/2QAKAMj/kQAKAMn/kQAKAM3/WAAKAM//sAALABP/7AALABf/5QALABn/6QALACb/6wALACr/6wALADL/6wALADT/6wALADwADwALAET/6wALAEb/3AALAEf/3QALAEj/3AALAE0AUgALAFL/3AALAFT/3AALAFb/6QALAFj/5wALAFn/5wALAFr/5wALAIj/6wALAJP/6wALAJT/6wALAJX/6wALAJb/6wALAJf/6wALAJn/6wALAJ4ADwALAKH/6wALAKL/6wALAKP/6wALAKT/6wALAKX/6wALAKb/6wALAKf/6wALAKj/3AALAKn/3AALAKr/3AALAKv/3AALAKz/3AALAK0AdgALALH/3AALALP/3AALALT/3AALALX/3AALALb/3AALALf/3AALALn/3AALALr/5wALALv/5wALALz/5wALAL3/5wALAML/6wALAMP/3AANAC3/kQANADf/hAANADn/5wANADv/zwANADz/rQANAD3/xwANAF3/6wANAJ7/rQAOABT/0AAOABX/zwAOABb/xgAOABr/mAAPAAX/SAAPAAr/WAAPABP/7AAPABf/2wAPACb/2AAPACr/2AAPADL/2AAPADT/2AAPADf/fwAPADj/6gAPADr/tgAPADz/hgAPAFf/5wAPAFr/2gAPAFz/vwAPAIj/2AAPAJP/2AAPAJT/2AAPAJX/2AAPAJb/2AAPAJf/2AAPAJn/2AAPAJr/6gAPAJv/6gAPAJz/6gAPAJ3/6gAPAJ7/hgAPAL7/vwAPAMD/vwAPAML/2AAPAMv/SAAPAMz/SAAQAAX/sAAQAAr/sAAQABT/wgAQABX/0gAQABb/vQAQABr/pAAQABz/6wAQAC3/hQAQADb/5wAQADf/ewAQADn/zgAQADr/4AAQADv/yQAQADz/hwAQAD3/wAAQAET/8AAQAEn/6AAQAFf/5AAQAFv/zQAQAF3/vQAQAJ7/hwAQAKH/8AAQAKL/8AAQAKP/8AAQAKT/8AAQAKX/8AAQAKb/8AAQAKf/8AAQAMz/sAARAAX/SAARAAr/WAARABP/7AARABf/2wARACb/2AARACr/2AARADL/2AARADT/2AARADf/fwARADj/6gARADn/jwARADr/tgARADz/hgARAFf/5wARAFn/0QARAFr/2gARAFz/vwARAIj/2AARAJP/2AARAJT/2AARAJX/2AARAJb/2AARAJf/2AARAJn/2AARAJr/6gARAJv/6gARAJz/6gARAJ3/6gARAJ7/hgARAL7/vwARAMD/vwARAML/2AARAMv/SAARAMz/SAASABL+SgASABf/rwASABn/7AASACT/vwASAC3/fQASADwAIwASAET/wwASAEb/tgASAEf/swASAEj/tgASAEr/swASAFD/2gASAFH/2gASAFL/tgASAFP/2gASAFT/tgASAFX/2gASAFb/zAASAFj/3gASAF3/5QASAIH/vwASAIL/vwASAIP/vwASAIT/vwASAIX/vwASAIb/vwASAIf/vwASAJ4AIwASAKH/wwASAKL/wwASAKP/wwASAKT/wwASAKX/wwASAKb/wwASAKf/wwASAKj/tgASAKn/tgASAKr/tgASAKv/tgASAKz/tgASAK0AjQASALH/tgASALL/2gASALP/tgASALT/tgASALX/tgASALb/tgASALf/tgASALn/tgASALr/3gASALv/3gASALz/3gASAL3/3gASAMP/tgATAAz/7AATADf/7AATAED/3wAUAA7/6QAUABD/4QAUADf/7AAUANT/5gAVABD/sgAVABf/7AAWAED/4gAXAAX/6gAXAAr/6gAXAA7/6QAXAA//5QAXABH/5QAXADf/6gAXAD//7AAXAED/6gAXAHH/6wAXAMz/6gAXANT/6gAaAA7/4wAaAA//cQAaABD/vAAaABH/cQAaABL/kwAaABf/zQAaACT/zwAaAC3/ngAaAET/3gAaAEb/0wAaAEf/0AAaAEj/0wAaAEr/0QAaAFL/0wAaAFT/0wAaAFb/5AAaAGP/5AAaAKj/0wAaAKn/0wAaAKr/0wAaAKv/0wAaAKz/0wAaALH/0wAaALP/0wAaALT/0wAaALX/0wAaALb/0wAaALf/0wAaALn/0wAaAMP/0wAaANH/mgAaANT/3wAbAED/5AAcAAz/7AAcAA//4QAcABH/4QAcAED/3gAcAGD/7AAgABT/1gAgABb/4QAgABr/sgAjADf/rQAjADz/1AAkAAX/zQAkAAr/zQAkACb/6gAkACr/6gAkADL/6gAkADT/6gAkADf/ZwAkADj/7AAkADn/pAAkADr/uAAkADz/egAkAD//vwAkAED/5gAkAEb/8gAkAEf/8wAkAEj/8gAkAEn/3QAkAFL/8gAkAFT/8gAkAFf/xwAkAFn/yAAkAFr/zgAkAFz/vQAkAGz/xgAkAHz/ywAkAIj/6gAkAJP/6gAkAJT/6gAkAJX/6gAkAJb/6gAkAJf/6gAkAJn/6gAkAJr/7AAkAJv/7AAkAJz/7AAkAJ3/7AAkAJ7/egAkAKj/8gAkAKn/8gAkAKr/8gAkAKv/8gAkAKz/8gAkALH/8gAkALP/8gAkALT/8gAkALX/8gAkALb/8gAkALf/8gAkALn/8gAkAL7/vQAkAMD/vQAkAML/6gAkAMP/8gAkAMv/wwAkAMz/zQAlADf/xwAlADv/1wAlADz/5gAlAD3/8wAlAED/3wAlAEn/9AAlAEr/8QAlAFb/8wAlAFf/9AAlAFn/9wAlAFr/9wAlAFv/4AAlAFz/9wAlAF3/7AAlAJ7/5gAlAK0AFgAlAL7/9wAlAMD/9wAmADf/4wAmADv/zgAmADz/7QAmAED/5wAmAEr/5gAmAFb/6wAmAFv/6AAmAF3/8gAmAJ7/7QAnAAz/6gAnAA//1gAnABH/1gAnACT/6gAnAC3/6QAnADf/tAAnADn/7QAnADr/8wAnADv/wAAnADz/yQAnAD3/1gAnAED/3AAnAET/9wAnAEr/9AAnAFv/6wAnAF3/8AAnAGD/6wAnAIH/6gAnAIL/6gAnAIP/6gAnAIT/6gAnAIX/6gAnAIb/6gAnAIf/6gAnAJ7/yQAnAKH/9wAnAKL/9wAnAKP/9wAnAKT/9wAnAKX/9wAnAKb/9wAnAKf/9wAoAA3/1QAoABD/rwAoACb/0AAoACr/0AAoADL/0AAoADT/0AAoADb/7QAoAET/9QAoAEb/zgAoAEf/zwAoAEj/zgAoAEn/zwAoAEr/7wAoAFL/zgAoAFT/zgAoAFb/8AAoAFf/vwAoAFj/6gAoAFn/kwAoAFr/oAAoAFz/gQAoAG7/rwAoAG//5QAoAIj/0AAoAJP/0AAoAJT/0AAoAJX/0AAoAJb/0AAoAJf/0AAoAJn/0AAoAKH/9QAoAKL/9QAoAKP/9QAoAKT/9QAoAKX/9QAoAKb/9QAoAKf/9QAoAKj/zgAoAKn/zgAoAKr/zgAoAKv/zgAoAKz/zgAoAK0AhwAoALH/zgAoALP/zgAoALT/zgAoALX/zgAoALb/zgAoALf/zgAoALn/zgAoALr/6gAoALv/6gAoALz/6gAoAL3/6gAoAL7/gQAoAMD/gQAoAML/0AAoAMP/zgAoAMj/rwAoAMn/rwAoAM//6QApAAn/6gApAA3/4wApAA//SAApABD/rwApABH/SAApABL/igApABf/0AApAB3/0QApAB7/yAApACT/ZQApACb/0AApACr/0AApAC3+8QApADL/0AApADT/0AApADb/5QApAET/XwApAEb/cAApAEf/cAApAEj/cAApAEn/vAApAEr/bgApAEz/4AApAE3/4AApAFD/eAApAFH/eAApAFL/cAApAFP/eAApAFT/cAApAFX/eAApAFb/hgApAFf/yAApAFj/ewApAFn/lwApAFr/mQApAFv/QgApAFz/kwApAF3/OwApAG7/rwApAG//7AApAIH/ZQApAIL/ZQApAIP/ZQApAIT/ZQApAIX/ZQApAIb/ZQApAIf/ZQApAIj/0AApAJP/0AApAJT/0AApAJX/0AApAJb/0AApAJf/0AApAJn/0AApAKH/pgApAKL/XwApAKP/XwApAKT/XwApAKX/XwApAKb/XwApAKf/XwApAKj/cAApAKn/oQApAKr/cAApAKv/cAApAKz/cAApAK0AvAApAK7/4AApAK//4AApALD/4AApALH/cAApALL/eAApALP/lgApALT/cAApALX/cAApALb/cAApALf/cAApALn/cAApALr/mAApALv/ewApALz/ewApAL3/ewApAL7/kwApAMD/kwApAMH/4AApAML/0AApAMP/cAApAMj/rwApAMn/rwApAM3/YgAqADf/0AAqADn/8QAqADz/2wAqAED/5gAqAEn/8gAqAEr/9QAqAFf/9QAqAJ7/2wArAET/9gArAEX/9gArAEb/8gArAEf/8QArAEj/8gArAEr/7QArAEv/9gArAEz/9gArAE3/9gArAE7/9gArAE//9QArAFD/9gArAFH/9gArAFL/8gArAFP/9gArAFT/8gArAFX/9gArAFb/9gArAFj/9AArAKD/9gArAKH/9gArAKL/9gArAKP/9gArAKT/9gArAKX/9gArAKb/9gArAKf/9gArAKj/8gArAKn/8gArAKr/8gArAKv/8gArAKz/8gArAK0ATwArAK7/9gArAK//9gArALD/9gArALH/8gArALL/9gArALP/8gArALT/8gArALX/8gArALb/8gArALf/8gArALn/8gArALr/9AArALv/9AArALz/9AArAL3/9AArAL//9gArAMH/9gArAMP/8gAsAET/9gAsAEX/9gAsAEb/8gAsAEf/8QAsAEj/8gAsAEr/7QAsAEv/9gAsAEz/9gAsAE3/9gAsAE7/9gAsAE//9QAsAFD/9gAsAFH/9gAsAFL/8gAsAFP/9gAsAFT/8gAsAFX/9gAsAFb/9gAsAFj/9AAsAKD/9gAsAKH/9gAsAKL/9gAsAKP/9gAsAKT/9gAsAKX/9gAsAKb/9gAsAKf/9gAsAKj/8gAsAKn/8gAsAKr/8gAsAKv/8gAsAKz/8gAsAK0ATwAsAK7/9gAsAK//9gAsALD/9gAsALH/8gAsALL/9gAsALP/8gAsALT/8gAsALX/8gAsALb/8gAsALf/8gAsALn/8gAsALr/9AAsALv/9AAsALz/9AAsAL3/9AAsAL//9gAsAMH/9gAsAMP/8gAtAA//8AAtABH/8AAtACT/8wAtAET/8wAtAEX/9wAtAEb/8gAtAEf/8gAtAEj/8gAtAEr/6AAtAEv/9wAtAEz/9gAtAE3/9gAtAE7/9wAtAE//9gAtAFD/8gAtAFH/8gAtAFL/8gAtAFP/8gAtAFT/8gAtAFX/8gAtAFb/8wAtAFj/8wAtAF3/7gAtAIH/8wAtAIL/8wAtAIP/8wAtAIT/8wAtAIX/8wAtAIb/8wAtAIf/8wAtAKD/9wAtAKH/8wAtAKL/8wAtAKP/8wAtAKT/8wAtAKX/8wAtAKb/8wAtAKf/8wAtAKj/8gAtAKn/8gAtAKr/8gAtAKv/8gAtAKz/8gAtAK0AVgAtAK7/9gAtAK//9gAtALD/9gAtALH/8gAtALL/8gAtALP/8gAtALT/8gAtALX/8gAtALb/8gAtALf/8gAtALn/8gAtALr/8wAtALv/8wAtALz/8wAtAL3/8wAtAL//9wAtAMH/9gAtAMP/8gAuAA3/2wAuABD/2gAuABIAJgAuACb/vQAuACr/vQAuADL/vQAuADT/vQAuADb/1wAuAEb/1AAuAEf/1gAuAEj/1AAuAEn/1QAuAEr/7gAuAFL/1AAuAFT/1AAuAFb/9AAuAFf/vQAuAFj/8gAuAFn/ogAuAFr/qAAuAFz/mQAuAG7/2gAuAG//4wAuAIj/vQAuAJP/vQAuAJT/vQAuAJX/vQAuAJb/vQAuAJf/vQAuAJn/vQAuAKj/1AAuAKn/1AAuAKr/1AAuAKv/1AAuAKz/1AAuAK0AcAAuALH/1AAuALP/1AAuALT/1AAuALX/1AAuALb/1AAuALf/1AAuALn/1AAuALr/8gAuALv/8gAuALz/8gAuAL3/8gAuAL7/mQAuAMD/mQAuAML/vQAuAMP/1AAuAMj/2gAuAMn/2gAuAM//2AAvAAX/TQAvAAr/WAAvAA3/ZwAvABD/YwAvABf/2gAvACb/vAAvACr/vAAvADL/vAAvADT/vAAvADb/8gAvADf/GgAvADj/2gAvADn/OgAvADr/gAAvADz/IQAvAD//agAvAED/4gAvAEb/5gAvAEf/6AAvAEj/5gAvAEn/2QAvAFL/5gAvAFT/5gAvAFf/vwAvAFj/9wAvAFn/fgAvAFr/mgAvAFz/ZwAvAGz/TgAvAG7/YwAvAG//bwAvAHz/TgAvAIj/vAAvAJP/vAAvAJT/vAAvAJX/vAAvAJb/vAAvAJf/vAAvAJn/vAAvAJr/2gAvAJv/2gAvAJz/2gAvAJ3/2gAvAJ7/IQAvAKj/5gAvAKn/5gAvAKr/5gAvAKv/5gAvAKz/5gAvALH/5gAvALP/5gAvALT/5gAvALX/5gAvALb/5gAvALf/5gAvALn/5gAvALr/9wAvALv/9wAvALz/9wAvAL3/9wAvAL7/ZwAvAMD/ZwAvAML/vAAvAMP/5gAvAMj/YwAvAMn/YwAvAMv/TgAvAMz/TQAvAM//XQAwAET/9gAwAEX/9gAwAEb/8gAwAEf/8QAwAEj/8gAwAEr/7QAwAEv/9gAwAEz/9gAwAE3/9gAwAE7/9gAwAE//9QAwAFD/9gAwAFH/9gAwAFL/8gAwAFP/9gAwAFT/8gAwAFX/9gAwAFb/9gAwAFj/9AAwAKD/9gAwAKH/9gAwAKL/9gAwAKP/9gAwAKT/9gAwAKX/9gAwAKb/9gAwAKf/9gAwAKj/8gAwAKn/8gAwAKr/8gAwAKv/8gAwAKz/8gAwAK0ATwAwAK7/9gAwAK//9gAwALD/9gAwALH/8gAwALL/9gAwALP/8gAwALT/8gAwALX/8gAwALb/8gAwALf/8gAwALn/8gAwALr/9AAwALv/9AAwALz/9AAwAL3/9AAwAL//9gAwAMH/9gAwAMP/8gAxAET/9gAxAEX/9gAxAEb/8gAxAEf/8QAxAEj/8gAxAEr/7QAxAEv/9gAxAEz/9gAxAE3/9gAxAE7/9gAxAE//9QAxAFD/9gAxAFH/9gAxAFL/8gAxAFP/9gAxAFT/8gAxAFX/9gAxAFb/9gAxAFj/9AAxAKD/9gAxAKH/9gAxAKL/9gAxAKP/9gAxAKT/9gAxAKX/9gAxAKb/9gAxAKf/9gAxAKj/8gAxAKn/8gAxAKr/8gAxAKv/8gAxAKz/8gAxAK0ATwAxAK7/9gAxAK//9gAxALD/9gAxALH/8gAxALL/9gAxALP/8gAxALT/8gAxALX/8gAxALb/8gAxALf/8gAxALn/8gAxALr/9AAxALv/9AAxALz/9AAxAL3/9AAxAL//9gAxAMH/9gAxAMP/8gAyAAz/6wAyAA//3QAyABH/3QAyACT/7AAyAC3/7wAyADf/uAAyADn/6wAyADr/8wAyADv/xgAyADz/yQAyAD3/2gAyAED/3gAyAEr/9AAyAFv/6gAyAF3/8QAyAGD/7AAyAIH/7AAyAIL/7AAyAIP/7AAyAIT/7AAyAIX/7AAyAIb/7AAyAIf/7AAyAJ7/yQAzAA//SAAzABD/tAAzABH/SAAzABL/pAAzABf/4QAzACT/lwAzAC3/GQAzADv/1gAzAD3/8gAzAED/6wAzAET/5QAzAEb/ywAzAEf/xAAzAEj/ywAzAEr/ygAzAFD/9wAzAFH/9wAzAFL/ywAzAFP/9wAzAFT/ywAzAFX/9wAzAFb/7wAzAG7/tAAzAIH/lwAzAIL/lwAzAIP/lwAzAIT/lwAzAIX/lwAzAIb/lwAzAIf/lwAzAKH/5QAzAKL/5QAzAKP/5QAzAKT/5QAzAKX/5QAzAKb/5QAzAKf/5QAzAKj/ywAzAKn/ywAzAKr/ywAzAKv/ywAzAKz/ywAzAK0ATwAzAK8AFwAzALAALgAzALH/ywAzALL/9wAzALP/ywAzALT/ywAzALX/ywAzALb/ywAzALf/ywAzALn/ywAzAMP/ywAzAMj/tAAzAMn/tAAzAM3/QAA0AAz/6wA0AA//3QA0ABH/3QA0ACT/7AA0AC3/7wA0ADf/uAA0ADn/6wA0ADr/8wA0ADv/xgA0ADz/yQA0AD3/2gA0AED/3gA0AEr/9AA0AFv/6gA0AF3/8QA0AGD/7AA0AIH/7AA0AIL/7AA0AIP/7AA0AIT/7AA0AIX/7AA0AIb/7AA0AIf/7AA0AJ7/yQA1AC3/8wA1ADf/1wA1ADv/7QA1ADz/8wA1AED/5AA1AET/7gA1AEb/2wA1AEf/2QA1AEj/2wA1AEr/4QA1AFD/9QA1AFH/9QA1AFL/2wA1AFP/9QA1AFT/2wA1AFX/9QA1AFb/8AA1AFj/8QA1AFv/9QA1AJ7/8wA1AKH/7gA1AKL/7gA1AKP/7gA1AKT/7gA1AKX/7gA1AKb/7gA1AKf/7gA1AKj/2wA1AKn/2wA1AKr/2wA1AKv/2wA1AKz/2wA1AK0AMgA1ALAAFQA1ALH/2wA1ALL/9QA1ALP/2wA1ALT/2wA1ALX/2wA1ALb/2wA1ALf/2wA1ALn/2wA1ALr/8QA1ALv/8QA1ALz/8QA1AL3/8QA1AMP/2wA2ADf/3wA2ADn/9QA2ADv/4AA2ADz/5QA2AED/5QA2AEn/4wA2AEr/8wA2AFb/9QA2AFf/3wA2AFn/4wA2AFr/5wA2AFv/2wA2AFz/4QA2AF3/7AA2AJ7/5QA2AL7/4QA2AMD/4QA3AAn/4gA3AA3/hAA3AA//fwA3ABD/ewA3ABH/fwA3ABL/owA3ABf/qwA3ABn/4QA3AB3/nAA3AB7/kAA3ACP/rAA3ACT/ZwA3ACb/uAA3ACr/uAA3AC3/JwA3ADL/uAA3ADT/uAA3ADb/7gA3ADwADQA3AET/TQA3AEb/QgA3AEf/QAA3AEj/QgA3AEn/tAA3AEr/NgA3AEz/1wA3AE3/1wA3AE//9wA3AFD/QwA3AFH/QwA3AFL/QgA3AFP/QwA3AFT/QgA3AFX/QwA3AFb/TQA3AFf/wgA3AFj/QwA3AFn/WwA3AFr/WQA3AFv/UAA3AFz/XQA3AF3/VgA3AG7/ewA3AG//lAA3AIH/ZwA3AIL/ZwA3AIP/ZwA3AIT/ZwA3AIX/ZwA3AIb/ZwA3AIf/ZwA3AIj/uAA3AJP/uAA3AJT/uAA3AJX/uAA3AJb/uAA3AJf/uAA3AJn/uAA3AJ4ADQA3AKH/qgA3AKL/TQA3AKP/TQA3AKT/TQA3AKX/TQA3AKb/TQA3AKf/TQA3AKj/QgA3AKn/pQA3AKr/QgA3AKv/QgA3AKz/QgA3AK0AwAA3AK7/1wA3AK//1wA3ALD/1wA3ALH/QgA3ALL/QwA3ALP/mQA3ALT/QgA3ALX/QgA3ALb/QgA3ALf/QgA3ALn/QgA3ALr/nAA3ALv/QwA3ALz/QwA3AL3/QwA3AL7/XQA3AMD/XQA3AMH/1wA3AML/uAA3AMP/QgA3AMj/ewA3AMn/ewA3AM3/gwA3AM//ogA4AA//6gA4ABH/6gA4ACT/7AA4AC3/9gA4AET/8QA4AEb/8gA4AEf/8QA4AEj/8gA4AEr/5wA4AEz/9wA4AE3/9wA4AE//9gA4AFD/8QA4AFH/8QA4AFL/8gA4AFP/8QA4AFT/8gA4AFX/8QA4AFb/8QA4AFj/8gA4AFv/9wA4AF3/7QA4AIH/7AA4AIL/7AA4AIP/7AA4AIT/7AA4AIX/7AA4AIb/7AA4AIf/7AA4AKH/8QA4AKL/8QA4AKP/8QA4AKT/8QA4AKX/8QA4AKb/8QA4AKf/8QA4AKj/8gA4AKn/8gA4AKr/8gA4AKv/8gA4AKz/8gA4AK0AVgA4AK7/9wA4AK//9wA4ALD/9wA4ALH/8gA4ALL/8QA4ALP/8gA4ALT/8gA4ALX/8gA4ALb/8gA4ALf/8gA4ALn/8gA4ALr/8gA4ALv/8gA4ALz/8gA4AL3/8gA4AMH/9wA4AMP/8gA5AA3/5wA5AA//jwA5ABD/zgA5ABH/jwA5ABL/uwA5ABf/4wA5ACT/pAA5ACb/6wA5ACr/6wA5AC3/YwA5ADL/6wA5ADT/6wA5AEAAEAA5AET/oQA5AEb/mQA5AEf/lAA5AEj/mQA5AEn/8AA5AEr/jwA5AFD/vQA5AFH/vQA5AFL/mQA5AFP/vQA5AFT/mQA5AFX/vQA5AFb/rAA5AFf/8gA5AFj/wgA5AFn/8AA5AFr/7gA5AFv/7QA5AFz/8gA5AF3/2AA5AGAAEwA5AG7/zgA5AIH/pAA5AIL/pAA5AIP/pAA5AIT/pAA5AIX/pAA5AIb/pAA5AIf/pAA5AIj/6wA5AJP/6wA5AJT/6wA5AJX/6wA5AJb/6wA5AJf/6wA5AJn/6wA5AKH/oQA5AKL/oQA5AKP/oQA5AKT/oQA5AKX/oQA5AKb/oQA5AKf/oQA5AKj/mQA5AKn/mQA5AKr/mQA5AKv/mQA5AKz/mQA5AK0AugA5ALAAIAA5ALH/mQA5ALL/vQA5ALP/mQA5ALT/mQA5ALX/mQA5ALb/mQA5ALf/mQA5ALn/mQA5ALr/wgA5ALv/wgA5ALz/wgA5AL3/wgA5AL7/8gA5AMD/8gA5AML/6wA5AMP/mQA5AMj/zgA5AMn/zgA5AM3/qgA6AA//tgA6ABD/4AA6ABH/tgA6ABL/1gA6ACT/uAA6ACb/8wA
gitextract_ksezc3pd/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── Dockerfile.local
├── LICENSE.txt
├── Pipfile
├── README.md
├── docker-compose.yml
└── notebooks/
├── 01_anaconda_notebooks.ipynb
├── 02_pandas.ipynb
├── 03_programacion.ipynb
├── 04_apis.ipynb
├── 05_web_scraping.ipynb
├── 06_intro_visualizacion.Rmd
├── 06_intro_visualizacion.html
├── 07_graficos.ipynb
├── 08_graficos_seaborn.ipynb
├── 09_graficos_mapas.ipynb
├── 10_numpy.ipynb
├── 11_pivotacion_tablas.ipynb
├── 12_bases_de_datos.ipynb
├── 13_produccion.ipynb
├── 14_entornos.ipynb
├── dat/
│ ├── README.md
│ ├── airquality.csv
│ ├── alquiler-madrid-distritos.csv
│ ├── alquiler-madrid-municipios.csv
│ ├── contaminacion_mad_201812.csv
│ ├── economist-countries-data.csv
│ ├── listings.csv
│ ├── milanuncios.html
│ ├── neighbourhoods.csv
│ ├── neighbourhoods.geojson
│ ├── ufos.csv
│ ├── venta-madrid-distritos.csv
│ └── venta-madrid-municipios.csv
└── soluciones/
├── 02_pandas.ipynb
├── 05_web_scraping.ipynb
├── 11_pivotacion_tablas.ipynb
└── paths.py
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (6,046K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 67,
"preview": "# These are supported funding model platforms\n\ngithub: [koldLight]\n"
},
{
"path": ".gitignore",
"chars": 47,
"preview": ".ipynb_checkpoints\ncompletado/\n.python-version\n"
},
{
"path": "Dockerfile.local",
"chars": 273,
"preview": "FROM python:3.7.6\nENV SOURCE_PATH /curso\nWORKDIR $SOURCE_PATH\nCOPY Pipfile* $SOURCE_PATH/\n\nCOPY . .\n\nRUN pip install pip"
},
{
"path": "LICENSE.txt",
"chars": 20127,
"preview": "Attribution-ShareAlike 4.0 International\n\n=======================================================================\n\nCreat"
},
{
"path": "Pipfile",
"chars": 293,
"preview": "[[source]]\nname = \"pypi\"\nurl = \"https://pypi.org/simple\"\nverify_ssl = true\n\n[dev-packages]\n\n[packages]\nsklearn = \"*\"\npan"
},
{
"path": "README.md",
"chars": 6747,
"preview": "[](https://mybinder.org/v2/gh/koldLight/curso-python-analisis-datos/master"
},
{
"path": "docker-compose.yml",
"chars": 238,
"preview": "version: '3.7'\n\nservices:\n curso:\n build:\n context: . \n dockerfile: Dockerfile.local \n volumes:\n -"
},
{
"path": "notebooks/01_anaconda_notebooks.ipynb",
"chars": 5579,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Anaconda y _notebooks_\"\n ]\n },"
},
{
"path": "notebooks/02_pandas.ipynb",
"chars": 25384,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Introducción a Pandas\"\n ]\n },\n"
},
{
"path": "notebooks/03_programacion.ipynb",
"chars": 30335,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {\n \"collapsed\": true\n },\n \"source\": [\n \"# Introdu"
},
{
"path": "notebooks/04_apis.ipynb",
"chars": 11096,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Introducción a las APIs\\n\",\n \""
},
{
"path": "notebooks/05_web_scraping.ipynb",
"chars": 16358,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Web scraping\\n\",\n \"\\n\",\n \"C"
},
{
"path": "notebooks/06_intro_visualizacion.Rmd",
"chars": 3927,
"preview": "---\ntitle: \"Visualización de datos\"\nauthor: \"Luz Frias\"\noutput:\n revealjs::revealjs_presentation:\n pandoc_args: [ \"-"
},
{
"path": "notebooks/06_intro_visualizacion.html",
"chars": 1318508,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"generator\" content=\"pandoc\">\n <meta name=\"author\" "
},
{
"path": "notebooks/07_graficos.ipynb",
"chars": 8120,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Graficos\"\n ]\n },\n {\n \"cell_"
},
{
"path": "notebooks/08_graficos_seaborn.ipynb",
"chars": 11378,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Gráficos con seaborn\\n\",\n \"\\n\""
},
{
"path": "notebooks/09_graficos_mapas.ipynb",
"chars": 11121,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Gráficos geoespaciales: mapas\\n\","
},
{
"path": "notebooks/10_numpy.ipynb",
"chars": 18543,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Numpy\\n\",\n \"\\n\",\n \"Hemos tr"
},
{
"path": "notebooks/11_pivotacion_tablas.ipynb",
"chars": 5137,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Pivotación de tablas\\n\",\n \"\\n\""
},
{
"path": "notebooks/12_bases_de_datos.ipynb",
"chars": 5879,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Bases de datos\\n\",\n \"\\n\",\n "
},
{
"path": "notebooks/13_produccion.ipynb",
"chars": 4612,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Producción\\n\",\n \"\\n\",\n \"Has"
},
{
"path": "notebooks/14_entornos.ipynb",
"chars": 11497,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Gestión de entornos\\n\",\n \"\\n\","
},
{
"path": "notebooks/dat/README.md",
"chars": 174,
"preview": "# Fuentes de datos\n\n## Precios de venta y alquiler históricos en Madrid\n\nLos 4 ficheros se han descargado de [idealista]"
},
{
"path": "notebooks/dat/airquality.csv",
"chars": 2814,
"preview": "\"ozone\",\"wind\",\"temp\",\"month\",\"day\",\"solar_r\"\n41,7.4,67,5,1,190\n36,8,72,5,2,118\n12,12.6,74,5,3,149\n18,11.5,62,5,4,313\n,1"
},
{
"path": "notebooks/dat/alquiler-madrid-distritos.csv",
"chars": 29845,
"preview": "\"distrito\",\"ano\",\"quarter\",\"precio\"\n\"Arganzuela\",2007,2,13.0665872953166\n\"Barajas\",2007,2,11.1998547051696\n\"Carabanchel\""
},
{
"path": "notebooks/dat/alquiler-madrid-municipios.csv",
"chars": 45327,
"preview": "\"municipio\",\"ano\",\"quarter\",\"precio\"\n\"Alcalá De Henares\",2009,4,8.56138561184977\n\"Alcobendas\",2009,4,NA\n\"Alcorcón\",2009,"
},
{
"path": "notebooks/dat/contaminacion_mad_201812.csv",
"chars": 510081,
"preview": "PROVINCIA;MUNICIPIO;ESTACION;MAGNITUD;PUNTO_MUESTREO;ANO;MES;D01;V01;D02;V02;D03;V03;D04;V04;D05;V05;D06;V06;D07;V07;D08"
},
{
"path": "notebooks/dat/economist-countries-data.csv",
"chars": 7162,
"preview": "\"\",\"Country\",\"HDI.Rank\",\"HDI\",\"CPI\",\"Region\"\n\"1\",\"Afghanistan\",172,0.398,1.5,\"Asia Pacific\"\n\"2\",\"Albania\",70,0.739,3.1,\""
},
{
"path": "notebooks/dat/listings.csv",
"chars": 2962203,
"preview": "id,name,host_id,host_name,neighbourhood_group,neighbourhood,latitude,longitude,room_type,price,minimum_nights,number_of_"
},
{
"path": "notebooks/dat/milanuncios.html",
"chars": 261291,
"preview": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"es\" lang=\"es\">\n<head><span id=\"dqqedxecxvrxyzyetrrd"
},
{
"path": "notebooks/dat/neighbourhoods.csv",
"chars": 2958,
"preview": "neighbourhood_group,neighbourhood\nArganzuela,Acacias\nArganzuela,Atocha\nArganzuela,Chopera\nArganzuela,Delicias\nArganzuela"
},
{
"path": "notebooks/dat/neighbourhoods.geojson",
"chars": 383923,
"preview": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-3.70584,4"
},
{
"path": "notebooks/dat/venta-madrid-distritos.csv",
"chars": 52438,
"preview": "\"distrito\",\"ano\",\"quarter\",\"precio\"\n\"Arganzuela\",2001,1,1920.40657543459\n\"Barajas\",2001,1,1661.89030566455\n\"Carabanchel\""
},
{
"path": "notebooks/dat/venta-madrid-municipios.csv",
"chars": 123848,
"preview": "\"municipio\",\"ano\",\"quarter\",\"precio\"\n\"Alcalá De Henares\",2002,2,1431.73463286578\n\"Alcobendas\",2002,2,2325.59394026927\n\"A"
},
{
"path": "notebooks/soluciones/02_pandas.ipynb",
"chars": 8734,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "notebooks/soluciones/05_web_scraping.ipynb",
"chars": 9662,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "notebooks/soluciones/11_pivotacion_tablas.ipynb",
"chars": 6506,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "notebooks/soluciones/paths.py",
"chars": 26,
"preview": "import os\n\nos.chdir(\"..\")\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the koldLight/curso-python-analisis-datos GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (18.9 MB), approximately 1.5M tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.