Cómo hacer que los pandas dibujen en el mismo gráfico con el mismo rango del eje y

Estoy tratando de trazar varios gráficos de barras verticalmente uno encima del otro. Debe haber un eje x etiquetado (con los días de la semana). El código que tengo hasta ahora es:

import pandas as pd import matplotlib.pyplot as plt import calendar df = pd.read_csv("health.csv", header = None, names = ['Physical', 'Emotional']) # Get Dayofweek index number (start with 6 for sunday) 6,0,1.... df['DayOfTheWeek'] = [(i+6) % 7 for i in range(len(df))] # Get a map to translate to day of week d = dict(zip(range(7),list(calendar.day_name))) df['DayOfTheWeek'] = df['DayOfTheWeek'].map(d) # Loop through the df (splitting week by week) for i in range(int(round(len(df)/7))): plt.ylim([0,10]) df.iloc[i*7:(i+1)*7].set_index('DayOfTheWeek').plot(kind='bar') plt.show() 

Esto tiene los siguientes problemas:

  1. Por algunas razones el primer gráfico producido está en blanco.
  2. Me gustaría que las subplots en el mismo gráfico estén separadas verticalmente en lugar de muchas plots separadas
  3. Mi dataframe tiene 39 filas, pero el método anterior no traza los últimos 4 puntos.

Los datos de entrada completos son:

 5,5 6,7 6,9 6,7 5,6 7,9 5,9 6,7 7,6 7,4 7,5 6,7 7,9 7,9 5,6 8,7 9,9 7,7 7,6 7,8 7,9 7,9 7,6 7,8 6,6 6,6 6,7 6,6 6,5 6,6 7,5 7,5 7,5 7,6 7,5 8,6 7,6 7,7 6,6 

Puede hacer esto configurando primero el diseño de su figura, luego pasando un objeto de ejes explícitos al método de trazado de pandas. Entonces condicionalmente solo muestro las tags del eje x en el último gráfico. También eliminé la asignación a los nombres de los días, esto se hace ahora a través de la ttwig directamente. ¡Obviamente se puede volver a colocar si es necesario por otras razones!

 import pandas as pd import matplotlib.pyplot as plt import calendar df = pd.read_csv("health.csv", header = None, names = ['Physical', 'Emotional']) # Get Dayofweek index number (start with 6 for sunday) 6,0,1.... df['DayOfTheWeek'] = [(i+6) % 7 for i in range(len(df))] df_calendar = calendar.Calendar(firstweekday=6) weeks = int(round(len(df)/7)) fig, axes = plt.subplots(weeks, 1, figsize=(6, weeks*3)) # Loop through the df (splitting week by week) for i in range(weeks): ax=axes[i] df.iloc[i*7:(i+1)*7].set_index('DayOfTheWeek').plot(kind='bar', ax=axes[i]) ax.set_ylim([0,10]) ax.set_xlim([-0.5,6.5]) ax.set_xticks(range(7)) if i == 0: ax.legend().set_visible(True) else: ax.legend().set_visible(False) if i == weeks-1: ax.set_xticklabels([calendar.day_name[weekday] for weekday in df_calendar.iterweekdays()]) ax.set_xlabel("Day of the week") else: ax.set_xticklabels([]) ax.set_xlabel("") plt.savefig("health.png") plt.show() 

salud

1. Por alguna razón el primer gráfico producido está en blanco.

Cuando llame a plt.ylim() , “establecerá los límites de y de los ejes actuales”. Lo hace llamando a plt.gca debajo del capó , que ” plt.gca la instancia actual de Axes (…) o creará una”. Ahora, en la primera iteración de su bucle, no existen ejes, por lo que crea uno nuevo. Luego pandas.DataFrame.plot procede a crear su propia figura, ignorando la existente. Así es como se obtiene una primera plot vacía.

La solución es simple: plt.ylim([0,10]) el orden de plt.ylim([0,10]) y la siguiente línea, o .plot(kind='bar', ylim=(0, 10)) directamente en .plot(kind='bar', ylim=(0, 10)) .

2. Me gustaría que las subplots en el mismo gráfico se separen verticalmente en lugar de muchas plots separadas

¿Quizás plt.subplots() es lo que estás buscando?

 n_weeks = 6 # See pt 3 for an elaboration on this fig, axs = plt.subplots(n_weeks, 1, figsize=(5, 12), sharex=True) # Record the names of the first 7 days in the dataset weekdays = df.head(7)['DayOfTheWeek'].values for weekno, ax in enumerate(axs): week = df.iloc[weekno*7:(weekno+1)*7] week = week.set_index('DayOfTheWeek') # The final week is incomplete and will mess up our plot unless # we force it to contain all the weekdays. week = week.loc[weekdays] week.plot(kind='bar', ylim=(0, 10), ax=ax, legend=False) # Only draw legend in the final Axis ax.legend() # Force tight layout fig.tight_layout() 

3. Mi dataframe tiene 39 filas, pero el método anterior no traza los últimos 4 puntos.

Intente imprimir los rangos que seleccione en su bucle y debería poder detectar el error. Es un error off-by-one 🙂

Spoiler / solución abajo!

 for i in range(int(round(len(df)/7))): print(df.iloc[i*7:(i+1)*7]) 

muestra que solo estás seleccionando semanas completas.

Nota: al copiar los datos de la pregunta, aparentemente perdí una fila. Debería haber 39. Sin embargo, las observaciones siguen en pie.

¡Vamos a inspeccionar lo que pasa! len(df) es 38, len(df) / 7 es 5.43, y round(len(df) / 7) es 5. Está redondeando a la semana completa más cercana. Si sus datos hubieran estado un día más, se redondearían hasta 6 como esperaba. Sin embargo, ese es un comportamiento algo quebradizo; a veces se redondea, otras veces, pero siempre quieres ver la última semana incompleta. Entonces, en lugar de hacer eso, le presentaré dos características interesantes : el operador // , que es una división de piso (siempre redondeando hacia abajo), y divmod , una función incorporada que hace simultáneamente la división de piso y le da el rest .

Mi solución sugerida usa divmod para contar las semanas incompletas:

 n_weeks, remaining_days = divmod(len(df), 7) n_weeks += min(1, remaining_days) for i in range(n_weeks): ...