Si está interesado en las finanzas y no le importa la programación, aprender este trío lo abrirá a un mundo completamente nuevo, que incluye, entre otros, la automatización tradinganálisis retrospectivo, inversión de factores, optimización de cartera y más.
Pienso en la combinación de Python, pandas y NumPy como un Microsoft Excel más robusto y flexible.
Python es extremadamente popular ya que permite a los desarrolladores crear programas rápidamente sin preocuparse por muchos detalles. Es un lenguaje de alto nivel. El problema con los lenguajes de alto nivel es que suelen ser lentos. La razón por la que Python es un lenguaje excelente para las finanzas es que obtienes el beneficio de la velocidad de desarrollo con una ejecución rápida, ya que tanto pandas como NumPy están altamente optimizados.
¿Qué es NumPy?
Supongamos que estamos calculando la relación precio-beneficio de 800 acciones. Tenemos dos columnas almacenadas en listas, siendo la primera el precio y la segunda las ganancias. Si tuviéramos que recorrer cada fila, nuestra CPU necesitaría 800 ciclos para convertir el cálculo en código de bytes y luego calcular la respuesta.
Vectorización
NumPy aprovecha la vectorización procesando múltiples cálculos a la vez usando Datos Múltiples de Instrucción Única (SIMD). Mientras que el bucle puede haber tomado 800 ciclos, el cálculo vectorizado puede tomar solo 200 ciclos o incluso menos.
NumPy también facilita nuestra vida como desarrollador, ya que simplifica muchas de las tareas cotidianas que debemos realizar en un conjunto de datos. Tome lo siguiente como un ejemplo abreviado del escenario de acciones 800 anterior:
Ejemplo usando Python y una lista de listas
price_earnings = [
['Company A', 10.0, 2.0],
['Company B', 30.0, 4.0],
['Company C', 50.0, 6.0],
['Company D', 70.0, 8.0],
['Company E', 90.0, 10.0]
]
pe_ratios = []
for row in price_earnings:
pe_ratio = row[1] / row[2]
pe_ratios.append(pe_ratio)
Ejemplo usando NumPy
pe_ratios = price_earnings[:,0] / price_earnings[:,1]
Si bien es posible que no comprenda la sintaxis exacta en este momento, entiende el punto: NumPy hace que tanto el desarrollo como la ejecución sean más rápidos. Puedes usar cualquiera de los operadores numéricos estándar de Python en su matemática vectorial. El único inconveniente, que es evidente cuando lo piensa, es que ambos vectores deben tener la misma forma y tipo, como un número entero de 5 elementos o un elemento flotante de 10 elementos.
Creación de una matriz NumPy
import numpy as np
price_earnings = [
['Company A', 10.0, 2.0],
['Company B', 30.0, 4.0],
['Company C', 50.0, 6.0],
['Company D', 70.0, 8.0],
['Company E', 90.0, 10.0]
]
## Creating array from prior values
company_table = np.array(price_earnings)
## Autogenrate a 1D array
array1d = np.arange(10)
## Autogenerate a 2D array into 2 rows by 5 columns
array2d = array1d.reshape(2,5)
NumPy también nos facilita el cálculo de varias estadísticas.
numpy.mean devuelve el promedio a lo largo del eje especificado. numpy.ndarray.max devuelve el valor máximo a lo largo de un eje dado. numpy.ndarray.min devuelve el valor mínimo a lo largo de un eje dado.
Hemos estado discutiendo matrices unidimensionales o unidimensionales. Puede pensar en estos como equivalentes a las columnas de Excel.
Las matrices bidimensionales o bidimensionales son como una hoja de Excel donde los valores están organizados por filas y columnas. max, min y mean calcularán el cálculo respectivo en todas las celdas de la matriz 2d a menos que especifiquemos lo contrario.
Si queremos obtener el máximo de una fila, especificaríamos ndarray.max(axis=1). Si queremos encontrar el máximo de cada columna, especificaríamos ndarray.max(axis=0) cambiando el ndarray con su matriz ndimensional. Al utilizar este tipo de operaciones, todos los campos deben ser del mismo tipo.
Selección de datos
Seleccionar datos en NumPy es similar a resaltar y copiar celdas en Excel. Los dos puntos representan toda la fila o columna. Al recuperar datos de una matriz NumPy 2D, utiliza el formato de fila y columna donde tanto la fila como la columna pueden tomar un valor o una lista:
numpy_2d_array[row,column]
Patrones de selección comunes
## Retrieve a single cell
cell = company_table[1,1]
## Retrieve the first row and all of the columns
row = company_table[0,:]
## Retreive all rows for the third column
column = company_table[:,2]
## Retreive two columns
two_columns = company_table[:,[0,2]]
## Retreive subsection
subsection = company_table[[0,2],[0,1]]
Selección de datos mediante matrices booleanas
A veces llamados vectores booleanos o máscaras booleanas, las matrices booleanas son una herramienta increíblemente poderosa. Los valores booleanos dan como resultado Verdadero o Falso en función de un operador de comparación como mayor que >, igual a == o menor que <.
Si quisiéramos verificar todas las acciones del ejemplo anterior con un pe_ratio de menos de 8, podríamos hacer lo siguiente:
low_pelow_pe_filter = pe_ratios < 8
Esto devolvería un ndarray 1D que contiene valores verdaderos y falsos. Agregué las otras columnas para mejorar la legibilidad, pero nuevamente, el resultado sería solo una sola columna: CompanyPriceEarningsP/E RatioPE < 8Company A1025.0TrueCompany B3047.5TrueCompany C5068.3FalseCompany D7088.75FalseCompany E90109.0False
Una vez que tenemos una máscara booleana, la aplicamos a la matriz original para filtrar los valores que queremos. Esto se conoce como indexación booleana.
low_pe_stocks = company_table[low_pe_filter]
EmpresaPrecioUtilidadesRatio P/UEmpresa A1025.0Empresa B3047.5
Trabajar con una máscara bidimensional no es diferente. Solo tenemos que asegurarnos de que la dimensión de la máscara sea la misma que la dimensión de la matriz que se está filtrando. Vea la siguiente matriz NumPy 2D con ejemplos de trabajo e incorrectos.
2d_array = np.array([
[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16],
[17,18,19,20]
])
1234567891011121314151617181920
array2d = np.array([
[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16],
[17,18,19,20]
])
rows_mask = [True, False, False, True, True]
columns_mask = [True, True, False, True]
# working examples
works1 = array2d[rows_mask]
works2 = array2d[:, columns_mask]
works3 = array2d[rows_mask, columns_mask]
# incorrect examples due to dimension mismatch
does_not_work1 = array2d[columns_mask]
does_not_work2 = array2d[row_mask, row_mask]
Modificación de datos
Ahora que sabemos cómo seleccionar datos, modificarlos se vuelve fácil. Para modificar la matriz anterior, simplemente podemos asignar un valor a nuestras selecciones.
# Change single value
array2d[0,0] = 5
array2d[0,0] = array2d[0,0] * 2
# Change row
array2d[0,:] = 0
# Change column
array2d[:,0] = 0
# Change a block
array2d[1:,[2,3]] = 0
# Change any value greater than 5 to 5
array2d[array2d > 5] = 5
# Change any value in column 2 greater than 5 to 5
array2d[array2d[:,1] > 5, 1] = 5
¿Qué es Pandas?
Pandas es una extensión de NumPy que se basa en lo que ya sabemos, admite operaciones vectorizadas y facilita aún más el análisis y la manipulación de datos.
Pandas tiene dos tipos de datos básicos: Series y DataFrames.
Tanto pandas Series como pandas DataFrames son similares a sus equivalentes NumPy, pero ambos tienen características adicionales que hacen que la manipulación de datos sea más fácil y divertida.
Tanto las matrices 2D NumPy como Panda DataFrames tienen un eje de fila (índice) y uno de columna; sin embargo, las versiones de pandas pueden tener etiquetas con nombre.
Hay tres componentes principales de un DataFrame:
Vea la siguiente imagen procedente de Selección de datos de un Pandas DataFrame
Creación de un marco de datos de pandas
En general, creará un marco de datos de pandas leyendo registros de un CSV, JSON, API o base de datos. También podemos construir uno a partir de un diccionario como se muestra a continuación.
dataframe.set_index le permite cambiar el índice si lo desea.
data = {'company': ['Company A', 'Company B', 'Company C', 'Company D', 'Company E'],
'price': [10, 30, 50, 70, 90],
'earnings': [2.0, 4.0, 6.0, 8.0, 10.0]
}
df = pd.DataFrame(data)
Crear una serie de pandas
Al igual que un marco de datos, normalmente leerá los datos de una serie de un CSV, JSON, API o base de datos. A modo de ejemplo, usaremos el marco de datos anterior para construir nuestra serie devolviendo una sola columna o fila de nuestro marco de datos.
# Create a series
column_series = df['company']
row_series = df.loc[0]
Los tipos de datos
Los datos generalmente se clasifican como continuos o categóricos. Los datos continuos son numéricos y representan una medida como la altura, el precio o el volumen. Los datos categóricos representan cantidades discretas, como el color de ojos, el sector industrial o el tipo de acción.
Los datos de pandas pueden ser muchos diferentes tipos de d con el más común que se muestra a continuación: General TypePandas String NameNumPy / Pandas ObjectBooleanboolnp.boolIntegerintnp.intFloatfloatnp.floatObjectobjectnp.objectDatetimedatetime64np.datetime64, pd.TimestampTimedeltatimedelta64np.timedelta64, pd.TimedeltaCategoricalcategorypd.Categorical
Comprender los datos
dataframe.info devuelve información común, como todos los dtypes, que es una forma abreviada de tipos de datos, la forma y otra información pertinente con respecto al marco de datos. Observe que el orden de las columnas no se conserva.
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 3 columns):
company 5 non-null object
earnings 5 non-null float64
price 5 non-null int64
dtypes: float64(1), int64(1), object(1)
memory usage: 192.0+ bytes
print(df.head(2))
print(df.tail(2))
company earnings price
0 Company A 2.0 10
1 Company B 4.0 30
company earnings price
3 Company D 8.0 70
4 Company E 10.0 90
Selección de datos
Podemos seleccionar datos en Pandas se puede hacer con uno de los tres indexadores []ubicación[] y iloc[]:
- Marco de datos[row_selection, column_selection] operador de indexación que se utiliza mejor para seleccionar columnas.
- DataFrame.loc[row_selection, column_selection] indexar filas y columnas por etiquetas, incluida la última columna
- DataFrame.iloc[row_selection, column_selection] indexar filas y columnas por entero excluyendo la última posición entera
- Serie.loc[index_selection] filas de índice por la etiqueta de índice.
- Serie.iloc[index_selection] filas de índice por la posición del entero.
Accesos directos para seleccionar datos
El marco de datos de acceso directo[‘label’] se puede utilizar al seleccionar una sola fila o una lista de filas. El marco de datos de acceso directo[‘label1′:’label3′] se puede utilizar para cortar filas; sin embargo, creo que usar dataframe.loc y dataframe.iloc al realizar la selección de filas, ya que hace que lo que está haciendo sea más explícito.
## Retrieve a single cell
cell = df.loc[1,'price']
## Retrieve the first row and all of the columns
row = df.loc[0,:]
## Retreive all rows for the third column
column = df.loc[:,'earnings']
column_short = df['earnings']
## Retreive a range of rows
rows = df.loc[1:3]
rows_short = df[1:3]
## Retreive two columns
two_columns = df.loc[:,['company','earnings']]
two_columns_by_position = df.iloc[:,[0,2]]
two_columns_short = df[['company','earnings']]
## Retrieve a range of columns
range_of_columns = df.loc[:,'earnings':'price']
range_of_columns_by_position = df.iloc[:,1:3]
## Retreive subsection
subsection = df.loc[0:2,['company','price']]
## Selecting from Series
## Selecting a single cell
cell = column_series.loc[1]
cell_short = column_series[1]
## Selecting a range of cells from a column
cell_range = column_series.loc[0:3]
cell_range_short = column_series[0:3]
## Selecting a range of cells from a row
cell_range = row_series.loc['company':'price']
cell_range_short = row_series['company':'price']
Comprender los datos
Los marcos de datos y las series de Pandas son objetos únicos, por lo que tienen métodos diferentes; sin embargo, se implementan muchos métodos tanto para marcos de datos como para series, como se muestra a continuación. Los números grandes se muestran en notación electrónica.
Serie.describe y Marco de datos.describe devuelve estadísticas descriptivas. Podemos usar el encadenamiento de métodos para devolver una serie de pandas y luego describirla como se muestra en el segundo ejemplo a continuación. Describir funciona de manera diferente en valores numéricos y no numéricos.
print(df.describe())
price earnings
count 5.000000 5.000000
mean 50.000000 6.000000
std 31.622777 3.162278
min 10.000000 2.000000
25% 30.000000 4.000000
50% 50.000000 6.000000
75% 70.000000 8.000000
max 90.000000 10.000000
print(df['price'].describe())
count 5.000000
mean 50.000000
std 31.622777
min 10.000000
25% 30.000000
50% 50.000000
75% 70.000000
max 90.000000
Name: price, dtype: float64
print(df['company'].describe())
count 5
unique 5
top Company B
freq 1
Name: company, dtype: object
Para algunos métodos de marco de datos, necesitaremos especificar si queremos calcular el resultado en el índice o las columnas. Por ejemplo, si sumamos los valores de las filas (índice), obtendremos un resultado para cada columna. Si sumamos todos los valores de las columnas, obtendremos un resultado para cada fila.
Para recordar los números de eje, recuerdo cómo hacer referencia a un marco de datos[0,1] donde 0 es la primera posición y representa filas y 1 es nuestra segunda posición y representa columnas. Y aunque 0 representa el cálculo de filas hacia abajo, a menudo también es el valor predeterminado y no es necesario indicarlo explícitamente. Si aún tiene problemas para recordar el orden, puede pensar que un 0 agregará otra fila y un 1 agregará otra columna.
## Sum across columns
df[['earnings','price']].sum(axis=1)
## Sum across rows
## Sum across rows calculating a value for each column. This is the default
df[['earnings','price']].sum()
df[['earnings','price']].sum(axis=0)
Operaciones estándar entre dos series
Al igual que con NumPy, podemos utilizar todos los Operaciones numéricas de Python en dos series panda que devuelven la serie resultante.
## Standard Operation examples
print(df[['earnings','price']] + 10)
earnings price
0 12.0 20
1 14.0 40
2 16.0 60
3 18.0 80
4 20.0 100
df['pe_ratio'] = df['price'] / df['earnings']
print(df)
company price earnings pe_ratio
0 Company A 10 2.0 5.000000
1 Company B 30 4.0 7.500000
2 Company C 50 6.0 8.333333
3 Company D 70 8.0 8.750000
4 Company E 90 10.0 9.000000
Asignación de valores
Ahora que entendemos la selección, la asignación se vuelve trivial. Es como NumPy, solo que podemos usar los accesos directos de selección de pandas. Esto incluye tanto la asignación como la asignación mediante indexación booleana. Vea los ejemplos a continuación de la sección de selección de tramas de datos. Tenga en cuenta que establecí el índice en empresa en lugar de usar un índice numérico.
# Assignment
df.set_index('company', inplace=True)
#df.loc['Company B', 'pe_ratio'] = 0.0
## Assignment to a single element
df.loc['Company B','price'] = 0.0
## Assignment to an entire row
df.loc['Company B',:] = 0
## Assignment to an entire column
df.loc[:,'earnings'] = 0.0
df['earnings'] = 1.0
## Assignment to a range of rows
df.loc['Company A':'Company C'] = 0.0
df['Company A':'Company C'] = 1.0
## Assignment to two columns
df.loc[:,['price','earnings']] = 2.0
df[['price','earnings']] = 3.0
## Assignment to a range of columns
df.loc[:,'earnings':'pe_ratio'] = 4.0
## Assignment to a subsection
df.loc['Company A':'Company C',['price','earnings']] = 5.0
## Assignment using boolean indexing
### Using boolean value
price_bool = df['price'] < 4.0
df.loc[price_bool,'price'] = 6.0
### Skipping boolean value
df.loc[df['price'] >= 4, 'price'] = 7.0