Taxonomy of D2C Providers

PayLink was the finance source we chose for Safe-Guard’s D2C program, and now they have launched their own D2C program, Olive.  This looks like a strong program, and I’m flattered they’ve kept many features from my blueprint.  I especially like name, which is a play on “all of” coverage.

“We estimate the market size of the ‘sweet spot’ for post-OEM warranty VSC sales will continue to grow to 109 million vehicles by 2024”

This is a $260 billion market.  Colonnade estimates that the “sweet spot” of vehicles less than 11 years old but past their OEM warranty is 87 million units, and growing.  That’s not to mention the 40 million sold each year without a service contract, at franchise and independent dealers.  I wrote about the different segments and how to value them in The Case for D2C.

Olive is positioned to address both segments, by partnering with automakers.  They claim two of the global top five.  I have reason to believe these are Volkswagen and Nissan, but I couldn’t find a source.  Like the original, Olive uses digital marketing and online origination through a consumer facing mobile-responsive (but not mobile-first) platform.

Not one to believe in coincidence, I reckoned that maybe there is only one winning formula, and this is it.  That would be bad news for APC, US Direct, Dialog Direct, Endurance, Infinite, Forever, Car Shield, Car Chex, Delta, Omega, and Concord.

If you are a data-science inclined tech strategy consultant, you might think of this as a seven-dimensional feature space

I taxonomized a bit in my first post on the topic, Direct to Consumer VSC Sales, and today we will work out a generalized framework.  This means mixing apples and oranges.  Many of the competitors I studied are “pure play” B2C.

Business to consumer (B2C) is everyday marketing, with branding, advertising, and SEO.  Direct to consumer (D2C) means that you were marketing through a channel, and now you’re going direct.  It also means that you have a channel conflict to manage.  Think of an auto finance company that does both direct and indirect lending.

My research found seven features that characterize a consumer-facing VSC vendor.  You can think of these as design choices for a new entrant:

    1. Administrator
    2. Payment plan
    3. Touch points
    4. Lead source
    5. Marketing
    6. Branding
    7. Eligibility

Contract administrator – At Safe-Guard, obviously, we were the admin, although we branded the contract for various OEM clients.  Endurance administers their own contract, and Forever Car has an exclusive partner.  Most B2C vendors offer multiple contracts, with a variety of coverage choices.  Tec Assured works for the dealer, offering whichever contract is sold in the dealership.

Payment plan – No one pays cash for a service contract.  APC has its own finance arm, and Dialog Direct is part of Budco.  The challenge here is the credit-card security (PCI) standards.  See My Shift in the Call Center.  Admins recognize the need for a subscription-based VSC but none has yet cracked the code (maybe JM&A).  Bundling the contract with a payment plan achieves the same effect.

Touch points – In the earlier post, I described three points in the lifecycle which a vendor could target.  This dovetails with the lead source, below.  If the vendor is working with an OEM or a dealer, they can focus on new customers who didn’t buy in the dealership.  They also have access to service dates.  The pure B2C vendors generally aim for the warranty’s end, or they don’t choose a touchpoint at all.

Lead source – If the vendor is working with an OEM or a dealer (or any affinity group) then that’s a source of leads.  This may also qualify as a “relationship” for spam-law purposes.  Then, there are the usual sources, like vehicle registration lists.  This brings us to marketing.

Marketing – The pure B2C vendors use pure pull marketing, developing a brand through SEO, social, and advertising.  Who could forget the Car Shield ads featuring Ice-T?  Old-school telemarketing and direct mail are still in use – love those postcards!  As in the earlier post, my favorite approach is digital marketing, from a lead list, with call-center backup, driven by a CRM like Salesforce or Nutshell.

Branding – Branding is complicated in this space.  The pure B2C vendors must develop an online brand, even though they sell multiple admins’ contracts.  On the other hand, the affinity vendors may develop the client’s brand, or they may create a separate brand as a way of mitigating channel conflict.

Eligibility – Depending on the touch point, the easiest way to deal with pre-existing conditions is to sell while there’s still OEM warranty remaining, or enforce a thirty-day waiting period. uses an inspection at Pep Boys, and there are also mobile inspection services.  Dealers using Tec Assured, obviously, have their own inspection capability.

Let’s demonstrate the framework with some worked examples:If you are a data-science inclined tech strategy consultant, you might think of this as a “feature space,” with each of the competitors staking out their territory on a seven-dimensional Go board.  You might also want to boil it down to three dimensions so your client can understand the diagram.

In the earlier post, I cited two broad categories: those that work with the dealer as a partner and lead source, and those that are pure B2C consumer-facing.  Here, I have shown a little more of the complexity.  Affinity marketing doesn’t stop with dealers, and some D2C vendors are hybrids.

If you’re a new entrant, this framework can help you structure your go-to market strategy.  If you’re an incumbent, you can play seven-D Go and outflank the competition.

Auto Auctions Disintermediated

Carvana acquired the Adesa auto auction last week.  Discussion on Twitter said this was not fair play, cutting into the supply line, and that dealers should take their business elsewhere.  I replied that there is already a movement to “disintermediate” the auctions, and that they will ultimately go the way of the stick shift.

Auctions are to wholesale what the test drive was to retail. 

If you think about it, the whole auction paradigm is incompetent, like the dark days of assembly plant inventory before JIT was invented.  It means that one dealer took my used X5 in trade, couldn’t retail it, and sold it at auction – where it was purchased by another dealer, and finally retailed to a new owner.

Think of the friction – the time lags, the transport, the fees.  It’s just insane.  The only reason I didn’t sell the car myself is that it’s a lot of bother, but I can easily sell person to person (P2P) through platforms like Shift and Tred.  I can also sell direct to a used-car specialist like CarMax or, yes, Carvana.

This diagram shows three ways to skip the auction:

Figures from NAAA show that auction volume has declined every year since 2016.  I understand they provide other services but, look – Carvana already ingests inventory at scale using its own facilities.  They handle two million vehicles a year, and Adesa will bring them to three.

The wholesale market will be conducted dealer-to-dealer, without physical auctions, on digital marketplaces like CDK CarSource and Cox Upside.  The only wholesale inventory will be in transit or recon, because the digital listing can flip instantly to a retail offer.

The Car Offer case is instructive.  Bruce Thompson developed Car Offer as a dealer-to-dealer marketplace, skipping the auction.  Car Gurus then bought the platform and converted it to a consumer site, skipping the selling dealer.

Auctions are to wholesale what the test drive was to retail.  Just as consumers are learning to buy cars online, so will dealers.  In fact, dealers should pick it up faster because they’re experts.

Lenders at Top of Funnel

Chase Auto recently rolled out a digital platform for car shopping … and financing.  I like it.  The link is here.  It seems that everyone today has a vehicle search page.  The original cast, Autotrader and, with about a dozen TPC competitors, are now joined by OEM sites, public dealer groups, and marketplaces from Roadster and Carvana.

“More vehicle shoppers than ever have started to look for vehicle financing before ever setting foot in a dealership.”

Competition hinges on which information the customer will seek first.  In an era of reduced purchasing power, many customers will want to “secure financing before going to the dealer.”  That’s the prompt on the Chase website.  There’s a prequal button right there between the Lariat and the XLT.

Don’t take my word for it, though.  This J.D. Power study found that nearly half of all customers shop for financing before visiting a dealer – 62% among Gen Z – and they start more than 30 days out.

This is probably a negative development for captives, and indirect finance in general.  Banks have a lower cost of capital and better rates.  Chase, as you know, is also popular as an indirect lender.  They say there’s no conflict with their dealer channel, but what if they had to choose?

The reach hierarchy, by customer base, is:

  • Banks – eight digits (ongoing)
  • Car Makers – millions of cars per year
  • Dealer Groups – hundreds of thousands

Banks have more customers, by an order of magnitude, than even the largest car makers.  Ten years’ worth of loyal Toyota drivers doesn’t approach Bank of America’s 66 million customers.  The same ranking goes for website reach, with the banks getting 120 to 190 million visits per month, while Carvana, Ford, and Autotrader each get twenty something.

Capital One, by the way, also has a shopping platform.  Ally has a dealer locator.  Bank of America has a redirect to Dealertrack.  Capital One is pretty shrewd about encouraging buyers to bring the app with them into the dealership, so they can update the deal as needed.  Mobile-first responsive is good, but an app is better.  Bank customers will carry their bank’s app.

Captives have the home field advantage once the customer is in the dealership and, likewise, their position online is downstream from the OEM brand.  Captives are advised to be front and center on their manufacturer’s website.

Dealer groups, like AutoNation, must rely on their own brand to draw customer attention.  In terms of unit sales, even the largest dealer groups fall below tenth-ranked Subaru.  Note that Lithia chose to develop a new brand, Driveway, for their online business.

Of course, none of these is a direct measure of financing intent.  Only a fraction of online banking traffic is looking for an auto loan.  The point is that they’re looking for the loan first, and then the car.

Claims Prediction with BQML

Did you know you could develop a machine learning model using SQL?  Google’s cloud data warehouse, Big Query, includes SQL support for machine learning with extensions like CREATE MODEL – by analogy with SQL DDL statement CREATE TABLE.

If you’re like me, you’re probably thinking, “why on Earth would I ever use SQL for machine learning?”  Google’s argument is that a lot of data people are handy with SQL, not so much with Python, and the data is already sitting in a SQL-based warehouse.

Big Query ML features all the popular model types from classifiers to matrix factorization, including an automated model picker called Auto ML.  There’s also the advantage of cloud ML in general, which is that you don’t have to build a special rig (I built two) for GPU support.

In this article, I am going to work a simple insurance problem using BQML.  My plan is to provide an overview that will engage both the Python people and the SQL people, so that both camps will get better results from their data warehouse.

  1. Ingest data via Google Cloud Storage
  2. Transformation and modeling in Big Query
  3. Access the results from a Vertex AI notebook

By the way, I have placed much of the code in a public repo.  I love grabbing up code samples from Analytics Vidhya and Towards Data Science, so this is my way of giving back.

Case Study: French Motor Third-Party Liability Claims

We’re going to use the French car insurance data from Wüthrich, et al., 2020.  They focus on minimizing the loss function (regression loss, not insurance loss) and show that decision trees outperform linear models because they capture interaction among the variables.

There are a few ways to handle this problem.  While Wüthrich treats it as a straightforward regression problem, Lorentzen, et al. use a composition of two linear models, one for claim frequency and a second for claim severity.  As we shall see, this approach follows the structure of the data.

Lorentzen focus on the Gini index as a measure of fitness.  This is supported by Frees, and also by the Allstate challenge, although it does reduce the problem to a ranking exercise.  We are going to follow the example of Dal Pozzolo, and train a classifier to deal with the imbalance issue.

Ingesting Query Data via Google Cloud Storage

First, create a bucket in GCS and upload the two CSV files.  They’re mirrored in various places, like here.  Next, in Big Query, create a dataset with two tables, Frequency and Severity.  Finally, execute this BQ LOAD script from the Cloud Shell:

bq load \
--source_format=CSV \
--autodetect \
--skip_leading_rows=1 \
french-cars:french_mtpl.Frequency \

The last two lines are syntax for the table and the GCS bucket/file, respectively.  Autodetect works fine for the data types, although I’d rather have NUMERIC for Exposure.  I have included JSON schemas in the repo.

It’s the most natural thing in the world to specify data types in JSON, storing this schema in the bucket with the data, but BQ LOAD won’t use it!  To utilize the schema file, you must create and load the table manually in the browser console.

Wüthrich specifies a number of clip levels, and Lorentzen implements them in Python.  I used SQL.  This is where we feel good about working in a data warehouse.  We have to JOIN the Severity data and GROUP BY multiple claims per policy, and SQL is the right tool for the job.

SET @@dataset_id = 'french_mtpl'; 
SELECT F.IDpol, ClaimNb, Exposure, Area, VehPower, VehAge, DrivAge, BonusMalus, VehBrand, VehGas, Density, Region, ClaimAmount
    Frequency AS F
    SUM(ClaimAmount) AS ClaimAmount
    IDpol) AS S
  F.IDpol = S.IDpol
UPDATE Combined
SET ClaimNb = 0
WHERE (ClaimAmount IS NULL AND ClaimNb >=1 );
UPDATE Combined
SET ClaimAmount = 0
WHERE (ClaimAmount IS NULL);
UPDATE Combined
SET ClaimNb = 1
WHERE ClaimNb > 4;
UPDATE Combined
SET Exposure = 1
WHERE Exposure > 1;
UPDATE Combined
SET ClaimAmount = 200000
WHERE ClaimAmount > 200000;
UPDATE Combined
SET Premium = ClaimAmount / Exposure

Training a Machine Learning Model with Big Query

Like most insurance data, the French MTPL dataset is ridiculously imbalanced.  Of 678,000 policies, fewer than 4% (25,000) have claims.  This means that you can be fooled into thinking your model is 96% accurate, when it’s just predicting “no claim” every time.

We are going to deal with the imbalance by:

  • Looking at a “balanced accuracy” metric
  • Using a probability threshold
  • Using class weights

Normally, with binary classification, the model will produce probabilities P and (1-P) for positive and negative.  In Scikit, predict_proba gives the probabilities, while predict gives only the class labels – assuming a 0.50 threshold.

Since the Allstate challenge, Dal Pozzolo and others have dealt with imbalance by using a threshold other than 0.50 – “raising the bar,” so to speak, for negative cases.  Seeking the right threshold can be a pain, but Big Query supplies a handy slider.

Sliding the threshold moves your false-positive rate up and down the ROC curve, automatically updating the accuracy metrics.  Unfortunately, one of these is not balanced accuracy.  You’ll have to work that out on your own.  Aim for a model with a good, concave ROC curve, giving you room to optimize.

The best way to deal with imbalanced data is to oversample the minority class.  In Scikit, we might use random oversampling, or maybe synthetic minority oversampling.  BQML doesn’t support oversampling, but we can get the same effect using class weights.  Here’s the script:

CREATE OR REPLACE MODEL`french-cars.french_mtpl.classifier1`
        ML.QUANTILE_BUCKETIZE(VehAge, 10) OVER() AS VehAge,
        ML.QUANTILE_BUCKETIZE(DrivAge, 10) OVER() AS DrivAge,
        CAST (VehPower AS string) AS VehPower,
        ML.STANDARD_SCALER(Log(Density)) OVER() AS Density,
    INPUT_LABEL_COLS = ['ClaimClass'], 
    DATA_SPLIT_METHOD = 'Random',
    CLASS_WEIGHTS = [STRUCT('NoClaim', 0.05), ('Claim', 0.95)]
FROM `french-cars.french_mtpl.Frequency`

I do some bucketizing, and CAST Vehicle Power to string, just to make the decision tree behave better.  Wüthrich showed that it only takes a few levels to capture the interaction effects.  This particular classifier achieves 0.63 balanced accuracy.  Navigate to the model’s “Evaluation” tab to see the metrics.

The OPTIONS are pretty standard.  This is XGBoost behind the scenes.  Like me, you may have used the XGB library in Python with its native API or the Scikit API.  Note how the class weights STRUCT offsets the higher frequency of the “no claim” case.

I can’t decide if I prefer to split the test set into a separate table, or just segregate it using WHERE on the Split column.  Code for both is in the repo.  BQML definitely prefers the Split column.

There are two ways to invoke Auto ML.  One is to choose Auto ML as the model type in the SQL script, and the other is to go through the Vertex AI browser console.  In the latter case, you will want a Split column.  Running Auto ML on tabular data costs $22 per server-hour, as of this writing.  The cost of regular BQML and data storage is insignificant.  Oddly, Auto ML is cheaper for image data.

Don’t forget to include the label column in the SELECT list!  This always trips me up, because I am accustomed to thinking of it as “special” because it’s the label.  However, this is still SQL and everything must be in the SELECT list.

Making Predictions with Big Query ML

Now, we are ready to make predictions with our new model.  Here’s the code:

    MODEL `french-cars.french_mtpl.classifier1`,
    WHERE Split = 'TEST'))

The model is treated like a FROM table, with its source data in a subquery.  Note that we trained on Split = ‘TRAIN’ and now we are using TEST.  The model returns multiple rows for each policy, giving the probability for each class:

This is a little awkward to work with.  Since we only want the claims probability, we must UNNEST it from its data structure and select prob where label is “Claim.” Support for nested and repeated data, i.e., denormalization, is typical of data warehouse systems like Big Query.

SELECT IDpol, probs.prob 
FROM pred, 
UNNEST (predicted_ClaimClass_probs) AS probs
WHERE probs.label = "Claim"

Now that we know how to use the model, we can store the results in a new table, JOIN or UPDATE an existing table, etc.  All we need for the ranking exercise is the probs and the actual Claim Amount.

Working with Big Query Tables in Vertex AI

Finally, we have a task that requires Python.  We want to measure, using a Gini index, how well our model ranks claims risk.  For this, we navigate to Vertex AI, and open a Jupyter notebook.  This is the same as any other notebook, like Google Colab, except that it integrates with Big Query.

from import bigquery
client = bigquery.Client(location="US")
sql = """SELECT * FROM `french_mtpl.Combined_Results` """ 
df = client.query(sql).to_dataframe()

The Client class allows you to run SQL against Big Query and write the results to a Pandas dataframe.  The notebook is already associated with your GCP project, so you only have to specify the dataset.  There is also a Jupyter magic cell command, %%bigquery.

Honestly, I think the hardest thing about Google Cloud Platform is just learning your way around the console.  Like, where is the “New Notebook” button?  Vertex used to be called “AI Platform,” and notebooks are under “Workbench.”

I coded my own Gini routine for the Allstate challenge, but the one from Lorentzen is better, so here it is.  Also, if you’re familiar with that contest, Allstate made us plot it upside down.  Corrado Gini would be displeased.

The actual claims, correctly sorted, are shown by the dotted line on the chart – a lot of zero, and then 2,500 claims.  Claims, as sorted by the model, are shown by the blue line.  The model does a respectable 0.30 Gini and 0.62 balanced accuracy.

Confusion Table:
       Pred_1 Pred_0 Total Pct. Correct
True_1   1731    771  2502     0.691847
True_0  29299  36420 65719     0.554178
Accuracy: 0.5592
Balanced Accuracy: 0.6230

Now that we have a good classifier, the next step would be to combine it with a severity model.  The classifier can predict which policies will have claims – or the probability of such – and the regressor can predict the amount.  Since this is already a long article, I am going to leave the second model as an exercise.

We have seen how to make a simple machine learning model using Big Query ML, starting from a CSV file in Google Cloud Storage, and proceeding through SQL and Python, to a notebook in Vertex AI.  We also discussed Auto ML, and there’s a bunch of sample code in the repo.