Laver's Law Revisited

A Data-Driven Investigation of Fashion Trends Over the Years

Fashion historian James Laver first wrote about “Laver’s Law” in his 1937 book Taste and Fashion. This theory attempts to categorize how clothing is perceived as it goes in and out of style.

Inspired by Laver, I wanted to see if it was possible to unearth some kind of formula that describes the modern fashion cycle, but computationally in order to account for the wealth of fashion imagery available over the internet. In short: I couldn’t. But what I made is still interesting, albeit messy.

I algorithmically clustered almost 300,000 runway images shot between 1989 and 2014 sourced from the excellent European Fashion Heritage Association. Below you can explore an interactive sample of this work, which contains around 1,000 images clustered into 50 styles.

Choose a Style Cluster

A Recap

As caveat-laden as our results are, let's zoom out for a moment and look at these style clusters in comparison to one another.

Note: The column "Common Designer" refers to the designer that appeared most frequently in that style cluster.


First off, I'll ackowledge with all humility that, other than that one job where I was employed under the title "data scientist" based on a technicality, I specialize in data visualization and don't generally do my own stunts. So, my process for investigating this topic was based in a lot of assumptions and limited by my knowledge of and access to available tools.

The Dataset

I started with a dataset of 472,465 runway photos from the excellent European Fashion Heritage Association (EFHA), specifically from the company Catwalk Pictures. I chose to use only Catwalk Pictures's photos for their consistent framing, long history of photos (dating from 1989 to 2014 within those hosted on the EFHA archive), and high quality.

I initially downloaded these photos to my personal machine before moving them into a dedicated Google Drive account to work with Colab. If I knew I was going to work in Colab to begin with, I would have started there, but I was still weighing my options at the time of that decision. I bring this up because sometime during this transferring, I lost photos from 2014 numbering in the tens of thousands.


I wanted to reduce the chances of images being clustered together because they appeared on similar-looking runways (or in the same shows). After some experimenting, I used Aaditya Vikram's background removal procedure, selected the largest remaining contour afterwards using OpenCV, and scaled the final image. This did a reasonable job, though admittedly it had trouble with particularly busy backgrounds and low-contrast shots.

I've heard of avant garde but this is ridiculous.

The Lovecraftian outputs wrought from processing those kinds of shots were more Magic the Gathering than Maison Martin Margiela (and there was a handful of more closely cropped photos to ruin my dreams of uniforming framing), so I applied another round of filters. With OpenCV, I filtered my background removed dataset until only images including one face of the approximate right scale in the approximate centre of the image remained. The trade-off to this method was that a number of photos featuring low-brimmed hats and eye-obscuring haircuts were filtered out. My final image count at the end of this exercise (and filtering out images without a valid year extractable from the name) was 272,361.

Some of my pre-processing code can be found here.


My choice and use of clustering method is probably where I made the most mistakes. Besides a lack of data science expertise, I was also determined to find a method that would work given less than Colab's 25GB of RAM and less than 200GB of storage space in my chosen plan.

I experimented with BIRCH but couldn't get the memory requirement to a manageable level. I couldn't get DenStream to do much of anything, and by doing a number of tests on subsets of the data, the data appeared to noisy to supply K-Means with a good k value that would scale if put in a streaming context. I'm trying to summarize a lot of experiments here that were scrapped months ago at various stages. It's very possible that there is a way to get one of these methods to work, and I just wasn't using the right parameters, dimensionality reduction techniques or feature extraction methods.

Results shown above came from using Steve Schmerler's imagecluster library. imagecluster provides utilities for feature extraction using VGG16, and clustering using scipy's hierarchical clustering function. I still needed a way to make the dataset size manageable within my Colab budget, but didn't want to choose a sample size arbitrarily.

I used PCA to reduce my the complexity of my feature data, and then iteratively clustered my data, each time discarding overly large clusters and singletons, until the remaining dataset (representing 27,563 images) could be clustered within my technical limitations. Parameters for clustering behaviour were selected each time based on apparent cohesiveness of the results and cluster size distribution. The results above came from this final round of clustering.

I've uploaded my clustering code here.

Next Steps

I approached this question the same way some impatient home cooks test if their spaghetti is cooked, and ended up with just as much of a mess. I'm going to step away from the problem for a bit and hopefully return with another appoach and a bit more knowledge in time for FW 2021.

More of my nonsense.