Sytuacja kobiet w IT w 2024 roku
24.06.202023 min
Veekesh Dhununjoy

Veekesh DhununjoyData Engineerlululemon

Zaawansowana wizualizacja danych z Matplotlib w Pythonie

Sprawdź, w jaki sposób przeprowadzić wizualizację danych z Matplotlib w Pythonie na takich przykładach, jak wykres konturowy, czy widżety.

Zaawansowana wizualizacja danych z Matplotlib w Pythonie

Obraz jest wart tysiąca słów, ale dobra wizualizacja jest warta miliony. Wizualizacja odgrywa fundamentalną rolę w komunikowaniu wyników z wielu dziedzin w dzisiejszym świecie. Bez odpowiednich wizualizacji bardzo trudno jest pokazać wyniki, zrozumieć złożone relacje między zmiennymi, czy opisać trendy w danych. W tym artykule zaczniemy od rysowania podstawowych wykresów za pomocą Matplotlib, a następnie przyjrzymy się kilku zaawansowanym technikom wizualizacji, takim jak zestaw narzędzi mplot3d (do generowania wykresów 3D) oraz widżetom.

Do zbadania różnych typów wykresów w bibliotece Matplotlib wykorzystano zestaw danych z raportu podatku od nieruchomości w Vancouver. Zestaw danych zawiera informacje o nieruchomościach z British Columbia Assessment oraz źródła miejskie, w tym identyfikator nieruchomości, rok budowy, kategorię strefy, aktualną wartość gruntu itp.

Linki do kodu są wymienione w dalszej części artykułu.

Podstawowe wykresy Matplotlib

Oto komendy, których często będę używał w poniższych przykładach:

plt.figure(): stwórz nowy wykres 
plt.plot(): narysuj x i y jako linie i/lub markery
plt.xlabel(): oznacz oś x
plt.ylabel(): oznacz oś y
plt.title(): Dodaj tytuł dla swoich osi
plt.grid(): Skonfiguruj linie siatki
plt.legend(): Umieść odnośniki do legendy na swoich osiach
plt.savefig(): Zapisz wykres na dysku
plt.show(): Wyświetl wykres
plt.clf(): Usuń wykres (może się przydać do narysowania kilku wykresów w tym samym kodzie)


Wykres liniowy

Wykres liniowy pokazuje dane w postaci szeregu punktów danych (zwanych markerami). Są one połączone liniami prostymi.

# Line plot.

# Importing matplotlib to plot the graphs.
import matplotlib.pyplot as plt

# Importing pandas for using pandas dataframes.
import pandas as pd

# Reading the input file.
df = pd.read_csv("property_tax_report_2018.csv")

# Removing the null values in PROPERTY_POSTAL_CODE.
df = df[(df['PROPERTY_POSTAL_CODE'].notnull())]

# Grouping by YEAR_BUILT and aggregating based on PID to count the number of properties for each year.
df = df[['PID', 'YEAR_BUILT']].groupby('YEAR_BUILT', as_index = False).count().astype('int').rename(columns = {'PID':'No_of_properties_built'})

# Filtering YEAR_BUILT and keeping only the values between 1900 to 2018.
df = df[(df['YEAR_BUILT'] >= 1900) & (df['YEAR_BUILT'] <= 2018)]

# X-axis: YEAR_BUILT
x = df['YEAR_BUILT']

# Y-axis: Number of properties built.
y = df['No_of_properties_built']

# Change the size of the figure (in inches).
plt.figure(figsize=(17,6))

# Plotting the graph using x and y with 'dodgerblue' color.
# Different labels can be given to different lines in the same plot.
# Linewidth determines the width of the line.
plt.plot(x, y, 'dodgerblue', label = 'Number of properties built', linewidth = 1)
# plt.plot(x2, y2, 'red', label = 'Line 2', linewidth = 2)

# X-axis label.
plt.xlabel('YEAR', fontsize = 16)

# Y-axis label.
plt.ylabel('Number of properties built', fontsize = 16)

# Title of the plot.
plt.title('Number of houses built between\n1900 and 2018', fontsize = 16)

# Grid
# plt.grid(True)
plt.grid(False)

# Legend for the plot.
plt.legend()

# Saving the figure on disk. 'dpi' and 'quality' can be adjusted according to the required image quality.
plt.savefig('Line_plot.jpeg', dpi = 400, quality = 100)

# Displays the plot.
plt.show()

# Clears the current figure contents.
plt.clf()

Powyższy fragment kodu można wykorzystać do utworzenia wykresu liniowego. Pandas Dataframe służy tutaj do wykonania podstawowych operacji na danych. Po odczytaniu i przetworzeniu wejściowego zestawu danych plt.plot() rysuje wykres liniowy z latami na osi x i liczbą zbudowanych nieruchomości na osi y.


Wykres słupkowy

Wykres słupkowy wyświetla dane, jako prostokątne słupki o wysokości lub długości proporcjonalnej do reprezentowanych przez nie wartości.

# Bar plot.

# Importing matplotlib to plot the graphs.
import matplotlib.pyplot as plt

# Importing pandas for using pandas dataframes.
import pandas as pd

# Reading the input file.
df = pd.read_csv("property_tax_report_2018.csv")

# Removing the null values in PROPERTY_POSTAL_CODE.
df = df[(df['PROPERTY_POSTAL_CODE'].notnull())]

# Grouping by YEAR_BUILT and aggregating based on PID to count the number of properties for each year.
df = df[['PID', 'YEAR_BUILT']].groupby('YEAR_BUILT', as_index = False).count().astype('int').rename(columns = {'PID':'No_of_properties_built'})

# Filtering YEAR_BUILT and keeping only the values between 1900 to 2018.
df = df[(df['YEAR_BUILT'] >= 1900) & (df['YEAR_BUILT'] <= 2018)]

# X-axis: YEAR_BUILT
x = df['YEAR_BUILT']

# Y-axis: Number of properties built.
y = df['No_of_properties_built']

# Change the size of the figure (in inches).
plt.figure(figsize=(17,6))

# Plotting the graph using x and y with 'dodgerblue' color.
# Different labels can be given to different bar plots in the same plot.
# Linewidth determines the width of the line.
plt.bar(x, y, label = 'Number of properties built', color = 'dodgerblue',  width = 1, align = 'center', alpha = 0.7)
# plt.bar(x2, y2, label = 'Bar 2', color = 'red',  width = 1)

# X-axis label.
plt.xlabel('YEAR', fontsize = 16)

# Y-axis label.
plt.ylabel('Number of properties built', fontsize = 16)

# Title of the plot.
plt.title('Number of houses built between\n1900 and 2018', fontsize = 16)

# Grid
# plt.grid(True)
plt.grid(axis='y')

# Legend for the plot.
plt.legend()

# Saving the figure on disk. 'dpi' and 'quality' can be adjusted according to the required image quality.
plt.savefig('Bar_plot.jpeg', dpi = 400, quality = 100)

# Displays the plot.
plt.show()

# Clears the current figure contents.
plt.clf()


Powyższy fragment kodu można wykorzystać do utworzenia wykresu słupkowego.


Histogram

Histogram to dokładne przedstawienie rozkładu danych liczbowych. Jest to oszacowanie rozkładu prawdopodobieństwa zmiennej ciągłej.

# Histogram

# Importing matplotlib to plot the graphs.
import matplotlib.pyplot as plt

# Importing pandas for using pandas dataframes.
import pandas as pd

# Reading the input file.
df = pd.read_csv("property_tax_report_2018.csv")

# Removing the null values in PROPERTY_POSTAL_CODE.
df = df[(df['PROPERTY_POSTAL_CODE'].notnull())]

# Grouping by YEAR_BUILT and aggregating based on PID to count the number of properties for each year.
df = df[['PID', 'YEAR_BUILT']].groupby('YEAR_BUILT', as_index = False).count().astype('int').rename(columns = {'PID':'No_of_properties_built'})

# Filtering YEAR_BUILT and keeping only the values between 1900 to 2018.
df = df[(df['YEAR_BUILT'] >= 1900) & (df['YEAR_BUILT'] <= 2018)]

# Change the size of the figure (in inches).
plt.figure(figsize=(17,6))

# X-axis: Number of properties built from 1900 to 2018.
# Y-axis: Frequency.
plt.hist(df['No_of_properties_built'],
         bins = 50,
         histtype='bar',
         rwidth = 1.0,
         color = 'dodgerblue',
         alpha = 0.8
       )

# X-axis label.
plt.xlabel('Number of properties built from 1900 to 2018', fontsize = 16)

# Y-axis label.
plt.ylabel('Frequency', fontsize = 16)

# Title of the plot.
plt.title('Distribution of the number of properties built between 1900 and 2018.', fontsize = 16)

# Grid
# plt.grid(True)
plt.grid(axis='y')

# Saving the figure on disk. 'dpi' and 'quality' can be adjusted according to the required image quality.
plt.savefig('Histogram.jpeg', dpi = 400, quality = 100)

# Displays the plot.
plt.show()

# Clears the current figure contents.
plt.clf()


Powyższy fragment kodu można wykorzystać do stworzenia histogramu.


Diagram kołowy

Diagram kołowy przedstawia podział na części w celu zilustrowania liczbowych proporcji. Na wykresie kołowym długość łuku każdego wycinka jest proporcjonalna do wielkości, którą dana część reprezentuje.

# Pie-chart.

# Importing matplotlib to plot the graphs.
import matplotlib.pyplot as plt

# Importing pandas for using pandas dataframes.
import pandas as pd

# Reading the input file.
df = pd.read_csv("property_tax_report_2018.csv")

# Filtering out the null values in ZONE_CATEGORY
df = df[df['ZONE_CATEGORY'].notnull()]

# Grouping by ZONE_CATEGORY and aggregating based on PID to count the number of properties for a particular zone.
df_zone_properties = df.groupby('ZONE_CATEGORY', as_index=False)['PID'].count().rename(columns = {'PID':'No_of_properties'})

# Counting the total number of properties.
total_properties = df_zone_properties['No_of_properties'].sum()

# Calculating the percentage share of each ZONE for the number of properties in the total number of properties.
df_zone_properties['percentage_of_properties'] = ((df_zone_properties['No_of_properties'] / total_properties) * 100)

# Finding the ZONES with the top-5 percentage share in the total number of properties.
df_top_10_zone_percentage = df_zone_properties.nlargest(columns='percentage_of_properties', n = 5)

# Change the size of the figure (in inches).
plt.figure(figsize=(8,6))

# Slices: percentage_of_properties.
slices = df_top_10_zone_percentage['percentage_of_properties']
# Categories: ZONE_CATEGORY.
categories = df_top_10_zone_percentage['ZONE_CATEGORY']
# For different colors: https://matplotlib.org/examples/color/named_colors.html
cols = ['purple', 'red', 'green', 'orange', 'dodgerblue']

# Plotting the pie-chart.
plt.pie(slices,
        labels = categories,
        colors=cols,
        startangle = 90,
#         shadow = True,
        # To drag the slices away from the centre of the plot.
        explode = (0.040,0,0,0,0),
        # To display the percent value using Python string formatting.
        autopct = '%1.1f%%'
       )

# Title of the plot.
plt.title('Top-5 zone categories with the highest percentage\nshare in the total number of properties.', fontsize = 12)

# Saving the figure on disk. 'dpi' and 'quality' can be adjusted according to the required image quality.
plt.savefig('Pie_chart.jpeg', dpi = 400, quality = 100)

# Displays the plot.
plt.show()

# Clears the current figure contents.
plt.clf()


Powyższy fragment kodu można wykorzystać do utworzenia wykresu kołowego.


Wykres punktowy

# Scatter plot.

# Importing matplotlib to plot the graphs.
import matplotlib.pyplot as plt

# Importing pandas for using pandas dataframes.
import pandas as pd

# Reading the input file.
df = pd.read_csv("property_tax_report_2018.csv")

# Removing the null values in PROPERTY_POSTAL_CODE.
df = df[(df['PROPERTY_POSTAL_CODE'].notnull())]

# Grouping by YEAR_BUILT and aggregating based on PID to count the number of properties for each year.
df = df[['PID', 'YEAR_BUILT']].groupby('YEAR_BUILT', as_index = False).count().astype('int').rename(columns = {'PID':'No_of_properties_built'})

# Filtering YEAR_BUILT and keeping only the values between 1900 to 2018.
df = df[(df['YEAR_BUILT'] >= 1900) & (df['YEAR_BUILT'] <= 2018)]

# X-axis: YEAR_BUILT
x = df['YEAR_BUILT']

# Y-axis: Number of properties built.
y = df['No_of_properties_built']

# Change the size of the figure (in inches).
plt.figure(figsize=(17,6))

# Plotting the scatter plot.
# For different types of markers: https://matplotlib.org/api/markers_api.html#module-matplotlib.markers
plt.scatter(x, y, label = 'Number of properties built',s = 200, color = 'red',
            alpha = 0.8, marker = '.', edgecolors='black')

# X-axis label.
plt.xlabel('YEAR', fontsize = 16)

# Y-axis label.
plt.ylabel('Number of properties built', fontsize = 16)

# Title of the plot.
plt.title('Number of houses built between\n1900 and 2018', fontsize = 16)

# Grid
# plt.grid(True)
plt.grid(axis='y')

# Legend for the plot.
plt.legend()

# Saving the figure on disk. 'dpi' and 'quality' can be adjusted according to the required image quality.
plt.savefig('Scatter_plot.jpeg', dpi = 400, quality = 100)

# Displays the plot.
plt.show()

# Clears the current figure contents.
plt.clf()


Powyższy fragment kodu można wykorzystać do utworzenia wykresu punktowego.


Praca z obrazami

Link do pobrania obrazu testowego Lenna. (Źródło: Wikipedia)

# Reading, displaying and saving an image.

# Importing matplotlib pyplot and image.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

# Reading the image from the disk.
image = mpimg.imread('Lenna_test_image.png')

# Displaying the image.
plt.imshow(image)

# Saving the image on the disk.
plt.imsave('save_Lenna.png', image, format = 'png')

Wykresy 3D przy użyciu Matplotlib

Wykresy 3D odgrywają istotną rolę w wizualizacji złożonych danych w trzech lub więcej wymiarach.


Wykres punktowy 3D

'''
==============
3D scatterplot
==============
Demonstration of a basic scatterplot in 3D.
'''

# Import libraries
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import numpy as np
import pandas as pd

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Get the Property Tax Report dataset
# Dataset link: https://data.vancouver.ca/datacatalogue/propertyTax.htm
data = pd.read_csv('property_tax_report_2018.csv')

# Extract the columns and do some transformations
yearWiseAgg = data[['PID','CURRENT_LAND_VALUE']].groupby(data['YEAR_BUILT']).agg({'PID':'count','CURRENT_LAND_VALUE':'sum'})
yearWiseAgg = yearWiseAgg.reset_index().dropna()

# Define colors as red, green, blue
colors = ['r', 'g', 'b']

# Get only records which have more than 2000 properties built per year
morethan2k = yearWiseAgg.query('PID>2000')

# Get shape of dataframe
dflen = morethan2k.shape[0]

# Fetch land values from dataframe
lanvalues = (morethan2k['CURRENT_LAND_VALUE']/2e6).tolist()

# Create a list of colors for each point corresponding to x and y
c_list = []
for i,value in enumerate(lanvalues):
    if value>0 and value<1900:
        c_list.append(colors[0])
    elif value>=1900 and value<2900:
        c_list.append(colors[1])
    else:
        c_list.append(colors[2])

# By using zdir='y', the y value of these points is fixed to the zs value 0
# and the (x,y) points are plotted on the x and z axes.
ax.scatter(morethan2k['PID'], morethan2k['YEAR_BUILT'], morethan2k['CURRENT_LAND_VALUE']/2e6,c=c_list)

# Set labels according to axis
plt.xlabel('Number of Properties')
plt.ylabel('Year Built')
ax.set_zlabel('Current land value (million)')

# Create customized legends 
legend_elements = [Line2D([0], [0], marker='o', color='w', label='No. of Properties with sum of land value price less than 1.9 millions',markerfacecolor='r', markersize=10),
                  Line2D([0], [0], marker='o', color='w', label='Number of properties with their land value price less than 2.9 millions',markerfacecolor='g', markersize=10),
                   Line2D([0], [0], marker='o', color='w', label='Number of properties with their land values greater than 2.9 millions',markerfacecolor='b', markersize=10)
                  ]
                   
# Make legend
ax.legend(handles=legend_elements, loc='best')

plt.show()

Wykresy punktowe w 3D są używane do umieszczania punktów danych na trzech osiach, aby pokazać związek między trzema zmiennymi. Każdy wiersz w tabeli danych jest reprezentowany przez znacznik, którego pozycja zależy od jego wartości w kolumnach ustawionych na osiach X, Y i Z.


Wykres liniowy 3D

'''
==============
3D lineplot
==============
Demonstration of a basic lineplot in 3D.
'''

# Import libraries
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt

# Set the legend font size to 10
mpl.rcParams['legend.fontsize'] = 10

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Create data point to plot
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)

# Plot line graph 
ax.plot(x, y, z, label='Parametric curve')

# Set default legend
ax.legend()

plt.show()

Wykresy liniowe 3D mogą być używane w przypadkach, gdy mamy jedną zmienną, która stale rośnie lub maleje. Zmienną tą można umieścić na osi Z natomiast zmianę pozostałych dwóch zmiennych można zaobserwować na osi X i osi Y względem osi Z.Na przykład, jeśli korzystamy z danych zmieniających się w czasie (takich jak np. ruch planet), czas można umieścić na osi Z a zmianę pozostałych dwóch zmiennych można zaobserwować na podstawie wizualizacji.


Podwykresy 3D

'''
====================
3D plots as subplots
====================
Demonstrate including 3D plots as subplots.
'''

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
import numpy as np


# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))

#===============
#  First subplot
#===============
# set up the axes for the first plot
ax = fig.add_subplot(1, 2, 1, projection='3d')

# plot a 3D surface like in the example mplot3d/surface3d_demo
# Get equally spaced numbers with interval of 0.25 from -5 to 5
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
# Convert it into meshgrid for plotting purpose using x and y
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
fig.colorbar(surf, shrink=0.5, aspect=10)

#===============
# Second subplot
#===============
# set up the axes for the second plot
ax = fig.add_subplot(1, 2, 2, projection='3d')

# plot a 3D wireframe like in the example mplot3d/wire3d_demo
# Return a tuple X, Y, Z with a test data set
X, Y, Z = get_test_data(0.05)
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)

plt.show()

Powyższy fragment kodu można wykorzystać do utworzenia wielu wykresów 3D jako podwykresów na tej samej ilustracji. Oba można analizować niezależnie.


Wykres konturowy

'''
==============
Contour Plots
==============
Plot a contour plot that shows intensity
'''

# Import libraries
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Get test data
X, Y, Z = axes3d.get_test_data(0.05)

# Plot contour curves
cset = ax.contour(X, Y, Z, cmap=cm.coolwarm)

# Set labels
ax.clabel(cset, fontsize=9, inline=1)

plt.show()

Powyższy fragment kodu można wykorzystać do tworzenia wykresów konturowych. Takich wykresów można użyć do przedstawienia powierzchni 3D w formacie 2D. Biorąc pod uwagę wartość dla osi Z rysujemy linie do połączenia współrzędnych (x, y) tam, gdzie występuje wartość z. Wykresy konturowe są zwykle używane dla zmiennych ciągłych, a nie cech jakościowych.


Wypełniony wykres konturowy

'''
==============
Contour Plots
==============
Plot a contour plot that shows intensity
'''
# Import libraries
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Get test data
X, Y, Z = axes3d.get_test_data(0.05)

# Plot contour curves
cset = ax.contourf(X, Y, Z, cmap=cm.coolwarm)

# Set labels
ax.clabel(cset, fontsize=9, inline=1)

plt.show()


Powyższy fragment kodu można wykorzystać do tworzenia wypełnionego wykresu konturowego.


Wykres powierzchniowy

"""
========================
Create 3d surface plots
========================
Plot a contoured surface plot 
"""

# Import libraries
from mpl_toolkits.mplot3d import Axes3D 
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np

# Create figures object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Make data.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)

# Customize the z axis.
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()

Powyższy fragment kodu można wykorzystać do tworzenia wykresów powierzchniowych, używanych do drukowania danych 3D. Takie wykresy ukazują związek między wyznaczoną zmienną zależną (Y) i dwiema zmiennymi niezależnymi (X i Z), zamiast pokazywać poszczególne punkty danych. Praktycznym zastosowaniem powyższego wykresu byłaby wizualizacja sposobu, w jaki zbiega się wynik metody gradientu prostego.


Wykres powierzchniowy trójkątny

'''
======================
Triangular 3D surfaces
======================
Plot a 3D surface with a triangular mesh.
'''
# Import libraries
from mpl_toolkits.mplot3d import Axes3D  
import matplotlib.pyplot as plt
import numpy as np

# Create figures object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Set parameters
n_radii = 8
n_angles = 36

# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)[..., np.newaxis]

# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage,  so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())

# Compute z to make the pringle surface.
z = np.sin(-x*y)

# Plot triangular meshed surface plot
ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)

plt.show()

Powyższy fragment kodu można wykorzystać do utworzenia wykresu powierzchniowego trójkątnego.


Wykres poligonalny

'''
==============
Polygon Plots
==============
Plot a polygon plot
'''
# Import libraries
from mpl_toolkits.mplot3d import Axes3D  
from matplotlib.collections import PolyCollection
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
import numpy as np

# Fixing random state for reproducibility
np.random.seed(19680801)

def cc(arg):
    '''
    Shorthand to convert 'named' colors to rgba format at 60% opacity.
    '''
    return mcolors.to_rgba(arg, alpha=0.6)


def polygon_under_graph(xlist, ylist):
    '''
    Construct the vertex list which defines the polygon filling the space under
    the (xlist, ylist) line graph.  Assumes the xs are in ascending order.
    '''
    return [(xlist[0], 0.), *zip(xlist, ylist), (xlist[-1], 0.)]

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Make verts a list, verts[i] will be a list of (x,y) pairs defining polygon i
verts = []

# Set up the x sequence
xs = np.linspace(0., 10., 26)

# The ith polygon will appear on the plane y = zs[i]
zs = range(4)

for i in zs:
    ys = np.random.rand(len(xs))
    verts.append(polygon_under_graph(xs, ys))

poly = PolyCollection(verts, facecolors=[cc('r'), cc('g'), cc('b'), cc('y')])
ax.add_collection3d(poly, zs=zs, zdir='y')

# Set labels
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(0, 10)
ax.set_ylim(-1, 4)
ax.set_zlim(0, 1)

plt.show()

Powyższy fragment kodu można wykorzystać do utworzenia wykresów poligonalnych.


Adnotacja w 3D

'''
======================
Text annotations in 3D
======================
Demonstrates the placement of text annotations on a 3D plot.
Functionality shown:
- Using the text function with three types of 'zdir' values: None,
  an axis name (ex. 'x'), or a direction tuple (ex. (1, 1, 0)).
- Using the text function with the color keyword.
- Using the text2D function to place text on a fixed position on the ax object.
'''
# Import libraries
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Demo 1: zdir
zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1))
xs = (1, 4, 4, 9, 4, 1)
ys = (2, 5, 8, 10, 1, 2)
zs = (10, 3, 8, 9, 1, 8)

for zdir, x, y, z in zip(zdirs, xs, ys, zs):
    label = '(%d, %d, %d), dir=%s' % (x, y, z, zdir)
    ax.text(x, y, z, label, zdir)

# Demo 2: color
ax.text(9, 0, 0, "red", color='red')

# Demo 3: text2D
# Placement 0, 0 would be the bottom left, 1, 1 would be the top right.
ax.text2D(0.05, 0.95, "2D Text", transform=ax.transAxes)

# Tweaking display region and labels
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')

plt.show()

Powyższy fragment kodu można wykorzystać do tworzenia adnotacji tekstowych na wykresach 3D. Jest to bardzo przydatne podczas tworzenia wykresów 3D, ponieważ w tym przypadku zmiana kątów wykresu nie zakłóca czytelności tekstu.


Dane 2D w wykresach 3D

"""
=======================
Plot 2D data on 3D plot
=======================
Demonstrates using ax.plot's zdir keyword to plot 2D data on
selective axes of a 3D plot.
"""

# Import libraries
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import numpy as np
import pandas as pd

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Get the Property Tax Report dataset
# Dataset link: https://data.vancouver.ca/datacatalogue/propertyTax.htm
data = pd.read_csv('property_tax_report_2018.csv')

# Extract the columns and do some transformations
yearWiseAgg = data[['PID','CURRENT_LAND_VALUE']].groupby(data['YEAR_BUILT']).agg({'PID':'count','CURRENT_LAND_VALUE':'sum'})
yearWiseAgg = yearWiseAgg.reset_index().dropna()

# Where zs takes either an array of the same length as xs and ys or a single value to place all points in the same plane 
# and zdir takes ‘x’, ‘y’ or ‘z’ as direction to use as z when plotting a 2D set.
ax.plot(yearWiseAgg['PID'],yearWiseAgg['YEAR_BUILT'], zs=0, zdir='z')

# Define colors as red, green, blue
colors = ['r', 'g', 'b']

# Get only records which have more than 2000 properties built per year
morethan2k = yearWiseAgg.query('PID>2000')

# Get shape of dataframe
dflen = morethan2k.shape[0]

# Fetch land values from dataframe
lanvalues = (morethan2k['CURRENT_LAND_VALUE']/2e6).tolist()

# Create a list of colors for each point corresponding to x and y
c_list = []
for i,value in enumerate(lanvalues):
    if value>0 and value<1900:
        c_list.append(colors[0])
    elif value>=1900 and value<2900:
        c_list.append(colors[1])
    else:
        c_list.append(colors[2])

# By using zdir='y', the y value of these points is fixed to the zs value 0
# and the (x,y) points are plotted on the x and z axes.
ax.scatter(morethan2k['PID'], morethan2k['YEAR_BUILT'], zs=morethan2k['CURRENT_LAND_VALUE']/2e6, zdir='y',c=c_list)

# Create customized legends 
legend_elements = [Line2D([0], [0], lw=2, label='Number of Properties built per Year'),
                   Line2D([0], [0], marker='o', color='w', label='No. of Properties with sum of land value price less than 1.9 millions',markerfacecolor='r', markersize=10),
                  Line2D([0], [0], marker='o', color='w', label='Number of properties with their land value price less than 2.9 millions',markerfacecolor='g', markersize=10),
                   Line2D([0], [0], marker='o', color='w', label='Number of properties with their land values greater than 2.9 millions',markerfacecolor='b', markersize=10)
                  ]
                   
# Make legend
ax.legend(handles=legend_elements, loc='best')

# Set labels according to axis
plt.xlabel('Number of Properties')
plt.ylabel('Year Built')
ax.set_zlabel('Current land value (million)')

plt.show()

Powyższy fragment kodu można wykorzystać do wykreślenia danych 2D na wykresie 3D. Pozwala to porównać wiele wykresów 2D w trójwymiarze.


Wykres słupkowy 2D w trójwymiarze

"""
========================================
Create 2D bar graphs in different planes
========================================
Demonstrates making a 3D plot which has 2D bar graphs projected onto
planes y=0, y=1, etc.
"""

# Import libraries
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import numpy as np
import pandas as pd

# Create figure object
fig = plt.figure()

# Get the current axes, creating one if necessary.
ax = fig.gca(projection='3d')

# Get the Property Tax Report dataset
# Dataset link: https://data.vancouver.ca/datacatalogue/propertyTax.htm
data = pd.read_csv('property_tax_report_2018.csv')

# Groupby Zone catrgory and Year built to seperate for each zone
newdata = data.groupby(['YEAR_BUILT','ZONE_CATEGORY']).agg('count').reset_index()

# Create list of years that are found in all zones that we want to plot
years = [1995,2000,2005,2010,2015,2018]

# Create list of Zone categoreis that we want to plot
categories = ['One Family Dwelling', 'Multiple Family Dwelling', 'Comprehensive Development']

# Plot bar plot for each category
for cat,z,c in zip(categories,[1,2,3],['r','g','b']):
    category = newdata[(newdata['ZONE_CATEGORY']==cat) & (newdata['YEAR_BUILT'].isin(years))]
    ax.bar(category['YEAR_BUILT'], category['PID'],zs=z, zdir='y', color=c, alpha=0.8)
    
# Set labels
ax.set_xlabel('Years')
ax.set_ylabel('Distance Scale')
ax.set_zlabel('Number of Properties')

# Create customized legends 
legend_elements = [Line2D([0], [0], marker='s', color='w', label='One Family Dwelling',markerfacecolor='r', markersize=10),
                  Line2D([0], [0], marker='s', color='w', label='Multiple Family Dwelling',markerfacecolor='g', markersize=10),
                   Line2D([0], [0], marker='s', color='w', label='Comprehensive Development',markerfacecolor='b', markersize=10)
                  ]
                   
# Make legend
ax.legend(handles=legend_elements, loc='best')

plt.show()

Powyższy fragment kodu można wykorzystać do utworzenia wielu wykresów słupkowych 2D w jednej przestrzeni 3D w celu analizy różnic.

Widżety w Matplotlib

Do tej pory mieliśmy do czynienia z wykresami statycznymi, gdzie użytkownik może jedynie wizualizować różne dane bez interakcji z nimi. Widżety natomiast wprowadzają interaktywność, co pozwala użytkownikowi na lepszą wizualizację, filtrowanie i porównywanie danych.


Widżet przycisku wyboru

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
import pandas as pd

df = pd.read_csv("property_tax_report_2018.csv")

# filter properties built on or after 1900
df_valid_year_built = df.loc[df['YEAR_BUILT'] >= 1900] 
# retrieve PID, YEAR_BUILT and ZONE_CATEGORY only
df1 = df_valid_year_built[['PID', 'YEAR_BUILT','ZONE_CATEGORY']]
# create 3 dataframes for 3 different zone categories
df_A = df1.loc[df1['ZONE_CATEGORY'] == 'Industrial']
df_B = df1.loc[df1['ZONE_CATEGORY'] == 'Commercial']
df_C = df1.loc[df1['ZONE_CATEGORY'] == 'Historic Area']
# retrieve the PID and YEAR_BUILT fields only
df_A = df_A[['PID','YEAR_BUILT']]
df_B = df_B[['PID','YEAR_BUILT']]
df_C = df_C[['PID','YEAR_BUILT']]
# Count the number of properties group by YEAR_BUILT
df2A = df_A.groupby(['YEAR_BUILT']).count()
df2B = df_B.groupby(['YEAR_BUILT']).count()
df2C = df_C.groupby(['YEAR_BUILT']).count()

# create line plots for each zone category 
fig, ax = plt.subplots()
l0, = ax.plot(df2A, lw=2, color='k', label='Industrial')
l1, = ax.plot(df2B, lw=2, color='r', label='Commercial')
l2, = ax.plot(df2C, lw=2, color='g', label='Historic Area')
# Adjusting the space around the figure
plt.subplots_adjust(left=0.2)
# Addinng title and labels
plt.title('Count of properties built by year')
plt.xlabel('Year Built')
plt.ylabel('Count of Properties Built')

#create a list for each zone category line plot
lines = [l0, l1, l2]

# make checkbuttons with all plotted lines with correct visibility
rax = plt.axes([0.05, 0.4, 0.1, 0.15])
# get the labels for each plot
labels = [str(line.get_label()) for line in lines]
# get the visibility for each plot
visibility = [line.get_visible() for line in lines]
check = CheckButtons(rax, labels, visibility)

# function to show the graphs based on checked labels
def func(label):
    index = labels.index(label)
    lines[index].set_visible(not lines[index].get_visible())
    plt.draw()

# on click event call function to display graph
check.on_clicked(func)

plt.show()

Jak widać na powyższym grafie, Matplotlib pozwala użytkownikowi oznaczyć, który wykres ma zostać wyświetlony za pomocą pola wyboru. Może to być szczególnie przydatne, gdy istnieje wiele różnych kategorii utrudniających porównania. Tego typu widżet ułatwia izolowanie i porównywanie wykresów, a także pozwala zmniejszyć bałagan.


Pasek przesuwania do kontrolowania właściwości wizualnych wykresów

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons

# configure subplot
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)

#set initial values of frequency and amplification
a0 = 5
f0 = 3
delta_f = 5.0
s = a0*np.cos(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')

# plot cosine curve
plt.axis([0, 1, -10, 10])

#configure axes
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)

# add slider for Frequency and Amplification
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)

# function to update the graph when frequency or amplification is changed on the slider
def update(val):
	# get current amp value
    amp = samp.val
    # get current freq value
    freq = sfreq.val
    # plot cosine curve with updated values of amp and freq
    l.set_ydata(amp*np.cos(2*np.pi*freq*t))
    # redraw the figure
    fig.canvas.draw_idle()
# update slider frequency
sfreq.on_changed(update)
# update amp frequency
samp.on_changed(update)

# reset slider to original values when reset button is pressed 
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')

# reset all variables
def reset(event):
    sfreq.reset()
    samp.reset()
button.on_clicked(reset)

rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)

# function to change color of graph
def colorfunc(label):
    l.set_color(label)
    fig.canvas.draw_idle()
# change color when radio button is clicked
radio.on_clicked(colorfunc)

plt.show()

Pasek przesuwania Matplotlib przydaje się do wizualizacji zmian parametrów na wykresach lub w równaniach matematycznych. Jak widać, pasek ten umożliwia użytkownikowi zmianę wartości zmiennych i natychmiastowe wyświetlenie zmiany.

Co dalej?

Jeśli interesuje Cię praca z bardziej interaktywnymi wykresami, które wyglądają bardziej nowocześnie, to zalecamy zapoznanie się z Dash od Plotly. To by było na tyle. Pełny kod (pliki Jupyter Notebook i Pythona) można znaleźć tutaj. Ze względu na ograniczenia Jupyter Notebooka wykresy interaktywne (czyli wykresy 3D i widżety) nie działają poprawnie. Dlatego wykresy 2D zostały umieszczone w Jupyter Notebook, a wykresy 3D i widżety to pliki .py.

Współautorzy:

Gaurav Prachchhak, Tommy Betz, Veekesh Dhununjoy, Mihir Gajjar.


Oryginał artykułu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>