Parallel Coordinates Plots with Plotly | by Darío Weitz | Towards Data Science

Data visualization is a tool for storytelling. It is common for a data scientist to have to find patterns or trends that are hidden in multidimensional datasets with multiple numerical variables. Parallel Plots or Parallel Coordinates Plots (PCPs) are data visualization charts appropriate for comparing numerous numerical variables simultaneously when those variables have different scales and/or different units of measurement.
In a PCP each numerical variable has its own axis. Besides, all axes are drawn vertical, parallel, and equally spaced. Records of the data set are represented as line segments connected across each axis. These segments form a polyline, which arises from the values taken by the different numerical variables on their corresponding axes.
The following figure shows a PCP with four numerical variables and six records for each variable. So we have four vertical, parallel, and equally spaced axes with their corresponding labels. There are six polylines made up of four connected points, one on each axis. Each polyline has its particular color according to the numerical value adopted by a fifth variable.
Patterns, trends, or clusters between the different numerical variables are analyzed according to the degree of parallelism between the polylines: a certain degree of parallelism in the lines joining parallel axes implies a positive relationship between the corresponding variables; crisscrossing (X-shaped) between the lines joining parallel axes implies a negative relationship between them; crisscrossing with no defined order implies that no particular relationship exists.
Any attempt to find patterns or relationships across the variables shown in Figure 1 would be misleading because of the different orders of magnitude between variable 4 and variables 1 and 3: we must proceed with a data normalization task prior to draw the figure. Normalization or Scaling techniques transform original data into a new scale that allows us to compare values of initially different magnitudes.
Two classical techniques for scaling are called Normalization and Standardization. Normalization scales each axis in PCP between its minimum and maximum values. With this procedure, the lowest value is set to 0, the highest to 1, and all other values in between are transformed accordingly. Standardization uses the mean and the standard deviation to transform the original numerical values into a common scale.
Storytelling with PCPs is usually difficult to communicate, even for technical and scientific audiences, because there’s no way to avoid the typical polylines jumble. The techniques of reordering and brushing could enhance the readability of the plot.
Reordering is based on the idea that relationships, clusters, or patterns between adjacent variables are more easily visualized than between non-adjacent ones. Reordering consists basically in changing the order of the vertical axes until some kind of pattern or relationship appears in the display.
Brushing minimizes clutter and reveals relationships by highlighting selected lines while others are faded. Diverging color scales are appropriate for those visualization tools that do not have a built-in method for brushing.
We used PCP to analyze data from a Kaggle Competition related to evaluating how some chemical properties affect the quality of red variants of the Portuguese “Vinho Verde” wine [Cortez et al., 2009]. The organoleptic quality of wines can be influenced by any of the following chemical or physical properties: alcohol, residual sugar, density, pH, fixed acidity, volatile acidity, citric acid, chlorides, free sulfur dioxide, total sulfur dioxide, and sulfates. Quality scores between 0 (Bad) and 10 (Excellent) were provided for each wine in the dataset.
First, we imported Plotly Express as px, and the libraries Numpy & Pandas as np and pd respectively. We included a function to switch off a particular warning.
import plotly.express as pximport numpy  as npimport pandas as pdpd.set_option('mode.chained_assignment', None)
Then we used the read_csv function to read the comma-separated values (csv) file into the corresponding dataframe (df). We used the describe() method to generate descriptive statistics (mean, std. dev., min, max, etc.) that summarize the central tendency, shape, and dispersion of the dataset. Then, we used isnull() to detect missing values.
df = pd.read_csv(path +'winequality-red.csv', sep=';',                 header = 0,                 names=['fixed acidity', 'volatile acidity',                         'citric acidity','residual sugar',                         'chlorides','free sulfur dioxide',                        'total sulfur dioxide','density', 'pH',                        'sulphates', 'alcohol', 'quality'])first10    = df.head(10)stat_basic = df.describe()count_null = df.isnull().sum()
The screenshot below shows the first ten records of the dataset:
We decided to eliminate the outliers using Z-scores. Remember that a Z-score is the number of standard deviations away from the mean for every data point of the dataset. We calculated Z-scores using stats.zscore(df) from the open source scientific library for Python ScyPy. After several tests, we selected the value 6 as the outlier filtering threshold.
from scipy import statszT  = np.abs(stats.zscore(df))df2 = df[(zT < 6).all(axis =1)]
We decided to split the dataset into high quality and low quality wines in order to reduce the number of lines, minimize clutter and improve pattern detection:
df_highq = df2[df2['quality'] >  6]df_lowq  = df2[df2['quality'] <= 6]
The screenshot of the dataset clearly shows the need for a scaling task before plotting the PCP. First of all, we normalized our dataset using the scikit-learn object MinMaxScaler. Remember that MinMaxScaler rescales the data from its original value so all the new values are within the range 0–1.
from sklearn.preprocessing import MinMaxScalerscaler = MinMaxScaler()df_highqN = df_highqdf_highqN[['fixed acidity', 'volatile acidity', 'citric acidity',           'residual sugar', 'chlorides','free sulfur dioxide',           'total sulfur dioxide','density',            'pH','sulphates','alcohol']]               =scaler.fit_transform(df_highqN[[                           'fixed acidity', 'volatile acidity',                           'citric acidity','residual sugar',                           'chlorides','free sulfur dioxide',                           'total sulfur dioxide','density', 'pH',                           'sulphates', 'alcohol']])
For the parallel coordinate plot in this article, the Plotly Express function is px.parallel_coordinates and the corresponding parameters are: data_frame; color= a name of a column in data_frame whose values are used to indicate the colors of lines; dimensions= names of columns in data_frame whose values are used to draw the lines; color_continuous_scale= to choose any of the built-in color scales; color_continuous_midpoint= to set an appropriate midpoint value when using a diverging color scale; title= to set the figure title.
fig0 = px.parallel_coordinates(df_highqN, color="quality",               dimensions=['fixed acidity', 'volatile acidity',                             'citric acidity', 'residual sugar',                             'total sulfur dioxide','density', 'pH',                             'sulphates', 'alcohol'                             ],               color_continuous_scale=px.colors.diverging.Tealrose,               color_continuous_midpoint=7.5,               title = 'PCP High Quality Wines  Without Reordering')fig0.show()
It’s really a mess. There is no way to get any useful information, so we decided to try different reordering schemes until we got Figure 3.
fig1 = px.parallel_coordinates(df_highqN, color="quality",             dimensions=['residual sugar', 'chlorides',                         'sulphates', 'volatile acidity', 'density',                         'pH',  'citric acidity', 'alcohol'                         ],             color_continuous_scale=px.colors.diverging.Tealrose,             color_continuous_midpoint=7.5,             title = 'PCP High Quality Wines  After Reordering')fig1.show()
The figure shows that high quality wines have low levels of residual sugar, chlorides, and sulphates, and high levels of alcohol and citric acid.
Let’s look at what happens with low-quality wines:
df_lowq2 = df_lowqdf_lowq2[['fixed acidity', 'volatile acidity', 'citric acidity',          'residual sugar', 'chlorides','free sulfur dioxide',          'total sulfur dioxide','density', 'pH',          'sulphates', 'alcohol']]  =             scaler.fit_transform(df_lowq2[[             'fixed acidity', 'volatile acidity', 'citric acidity',             'residual sugar', 'chlorides','free sulfur dioxide',             'total sulfur dioxide','density', 'pH',             'sulphates', 'alcohol']])fig2 = px.parallel_coordinates(df_lowq2, color="quality",           dimensions=['residual sugar', 'chlorides',                       'sulphates', 'volatile acidity',                       'alcohol', 'pH','density',                       'citric acidity'                       ],            color_continuous_scale=px.colors.diverging.RdYlBu,            color_continuous_midpoint=4,            title = 'PCP Low Quality Wines  After Reordering')fig2.show()
It seems that low quality wines have high levels of chlorides, sulphates, and volatile acidity.
Finally, we would like to know if scaling with the standardization technique improves the storytelling.
from sklearn.preprocessing import StandardScalerscalerST = StandardScaler()df_highqS = df_highqdf_highqS[['fixed acidity', 'volatile acidity', 'citric acidity',           'residual sugar', 'chlorides','free sulfur dioxide',           'total sulfur dioxide','density', 'pH',           'sulphates', 'alcohol']]  =               scalerST.fit_transform(df_highqS[[              'fixed acidity', 'volatile acidity', 'citric acidity',              'residual sugar', 'chlorides','free sulfur dioxide',              'total sulfur dioxide','density', 'pH',              'sulphates', 'alcohol']])fig3 = px.parallel_coordinates(df_highqS, color="quality",           dimensions=['residual sugar', 'chlorides',                       'sulphates', 'fixed acidity', 'density',                       'volatile acidity','pH',                       'citric acidity'                       ],           color_continuous_scale=px.colors.diverging.Tealrose,           color_continuous_midpoint=7.5,           title= 'PCP High Quality Wines  After Standardization')fig3.show()
As can be seen, the display is equivalent to that of Figure 3 with the additional difficulty of showing negative values for chemical and physical properties that do not correspond.
In summary, Parallel Coordinates Plots are very complex graphics that require several preprocessing steps. They are only recommended for data sets with a large number of numerical variables. Always use Normalization as the scaling technique. Their complexity does not allow to guarantee an adequate storytelling.
If you find this article of interest, please read my previous (https://medium.com/@dar.wtz):
Diverging Bars, Why & How, Storytelling with Divergences
towardsdatascience.com
Slope Charts, Why & How, Storytelling with Slopes