Loading...

Estimating Lithium-ion Battery Health

Introduction

Lithium-based batteries - their cycles characteristics and aging

Lithium-ion (or Li-ion) batteries are rechargeable batteries used for a variety of electronic devices, which range from eletric vehicles, smartphones, and even satellites.

However, despite their wide adoption, research isn’t mature enough to avoid problems with battery health and safety, and given the ubiquity of consumer electronics using the technology, this has led to some poor outcomes that range from poor user-experience to public safety concerns (see, for example, the Samsung Galaxy Note 7 explosions from 2016).

Dataset

In this example of predictive maintenance, we propose a data-driven method to estimate the health of a battery using the Li-ion battery dataset released by NASA.

This dataset includes information on Li-ion batteries over several charge and discharge cycles at room temperature. Charging was at a constant current (CC) at 1.5A until the battery voltage reached 4.2V and then continued in a constant voltage (CV) mode until the charge current dropped to 20mA.

Discharge was at a constant current (CC) level of 2A until the battery voltage fell to 2.7V.

You can download the Jupyter Notebook of the study here.

The dataset includes the following:

  • Voltage_measured: Battery’s terminal voltage (Volts) for charging and discharging cycles.

  • Current_measured: Battery’s output current (Amps) for charging and discharging cycles.

  • Temperature_measured: Battery temperature (degree Celsius).

  • Current_charge: Current measured at charger for charging cycles and at load for discharging cycles (Amps).

  • Voltage_charge: Voltage measured at charger for charging cycles and at load for discharging ones (Volts).

  • Start_time: Starting time of the cycle.

  • Time: Time in seconds after the starting time for the cycle (seconds).

  • Capacity: Battery capacity (Ahr) for discharging until 2.7V. Battery capacity is the product of the current drawn from the battery (while the battery is able to supply the load) until its voltage drops lower than a certain value for each cell.

We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.

Initialization

This example uses the following version of VerticaPy:

import verticapy as vp

vp.__version__
Out[2]: '1.1.0'

Connect to Vertica. This example uses an existing connection called VerticaDSN. For details on how to create a connection, see the Connection tutorial. You can skip the below cell if you already have an established connection.

vp.connect("VerticaDSN")

Before we import the data, we’ll drop any existing schemas of the same name.

vp.drop("battery_data", method="schema")
Out[3]: True

vp.create_schema("battery_data", True)
Out[4]: True

Let us now ingest the data.

battery5 = vp.read_csv("data/data.csv")

Warning

This example uses a sample dataset. For the full analysis, you should consider using the complete dataset.

Understanding the Data

Let’s examine our data. Here, we use head() to retrieve the first five rows of the dataset.

123
Voltage_measured
Float
100%
...
123
Current_measured
Float
100%
123
Capacity
Float
8%
12.66684542897547...-2.009718473655491.76231507040931
22.68499881852498...-2.012630175310671.81403112750839
32.69951692300468...-2.018190886256341.49084440504002
42.70742704244784...-2.011416896931541.3389145230207
52.80290487900751...-2.012457130797781.32902865651207
62.81015874386668...-2.013018078754651.3389145230207
72.86534003859311...-2.011122778452381.30779599469444
82.87532762204241...-2.013738977214551.43867093714806
92.87544665543895...-2.013812594021611.83464550821204
102.88060038984582...-2.01373878868311.76231507040931
112.88091852838522...-2.011205655083691.52852526297564
122.8898440837763...-2.013734836565121.30335735590382
132.89319852614925...-2.011454780510581.29788708064957
142.8997991086539...-2.012855013641421.3236741274999
152.90158446050215...-2.011648543866231.78844323353705
162.90322576197612...-2.013570190972821.52264732528438
172.90406473654365...-2.011178750874991.30341004386556
182.92177442536308...-2.012700059478851.30901536423074
192.93939223121491...-2.010724437947851.71053335113519
202.94061511365568...-2.013671485738021.31863389675329

Let’s perform a few aggregations with describe() to get a high-level overview of the dataset.

battery5.describe()
...
approx_75%
max
"Voltage_measured"...4.205895403298954.98472045064886
"Current_measured"...1.229468863928361.5215190253818
"Temperature_measured"...27.334491607579641.2725661150757
"Current_charge"...1.4981.9984
"Voltage_charge"...4.655.002
"Time"...7494.9663529411810807.219
"ambient_temp"...24.024.0
"Capacity"...1.741750339258281.85648742081816

To get a better idea of the changes between each cycle, we look at an aggregation at their start time, duration, and voltage at the beginning and the end of each cycle.

battery5["start_time"].describe()
value
name"start_time"
dtypetimestamp
count35288
min2008-04-02 13:08:17.000920
max2008-05-28 11:09:42.000045

To see how the voltage changes during the cycle, we extract the initial and final voltage measurements for each cycle.

battery5.analytic(
    func = "first_value",
    columns = "Voltage_measured",
    by = "start_time",
    order_by = {"Time": "asc"},
    name = "first_voltage_measured",
)
battery5.analytic(
    func = "first_value",
    columns = "Voltage_measured",
    by = "start_time",
    order_by = {"Time": "desc"},
    name = "last_voltage_measured",
)
cycling_info = battery5.groupby(
        columns = [
            "start_time",
            "type",
            "first_voltage_measured",
            "last_voltage_measured",
        ],
        expr = [
            "COUNT(*) AS nr_of_measurements",
            "MAX(Time) AS cycle_duration",
        ],
).sort("start_time")
cycling_info["cycle_id"] = "ROW_NUMBER() OVER(ORDER BY start_time)"
cycling_info.head(100)
📅
start_time
Timestamp(29)
100%
...
123
cycle_duration
Float(22)
100%
123
cycle_id
Integer
100%
12008-04-02 13:08:17.000920...7561.751
22008-04-02 15:25:41.000593...3072.8592
32008-04-02 16:37:51.000984...10516.03
42008-04-02 19:43:48.000405...3509.614
52008-04-02 20:55:40.000811...10401.9375
62008-04-03 00:01:06.000687...3651.6416
72008-04-03 01:12:38.000670...10397.897
82008-04-03 04:16:37.000375...3309.7198
92008-04-03 05:27:49.000125...10166.7189
102008-04-03 08:33:25.000702...3507.39110
112008-04-03 09:44:35.000078...10352.14111
122008-04-03 12:55:10.000686...3530.012
132008-04-03 14:06:43.000234...10683.6113
142008-04-03 17:17:16.000015...2917.35914
152008-04-03 18:28:47.000125...9667.51515
162008-04-03 21:28:14.000718...3531.7516
172008-04-03 22:38:27.000452...9901.39117
182008-04-04 01:38:15.000217...3429.04718
192008-04-04 02:48:06.000155...9370.67219
202008-04-04 05:48:08.000609...3231.2520
212008-04-04 06:58:00.000296...9491.34421
222008-04-04 09:57:19.000765...3231.2522
232008-04-04 11:06:50.000375...9969.68823
242008-04-04 15:05:59.000905...9525.2524
252008-04-04 17:56:27.000609...3491.01625
262008-04-04 19:05:19.000234...9738.6426
272008-04-04 22:01:54.000670...3310.34427
282008-04-04 23:10:25.000420...10629.8928
292008-04-05 02:20:26.000702...2744.89129
302008-04-05 03:28:57.000234...9606.45330
312008-04-05 06:25:01.000890...3114.031
322008-04-05 07:33:12.000734...9978.51632
332008-04-05 10:30:32.000311...2002.87533
342008-04-05 11:38:22.000140...9293.96834
352008-04-05 14:34:41.000468...3171.93835
362008-04-05 15:42:11.000531...9649.34436
372008-04-05 18:39:25.000217...3192.3937
382008-04-05 19:46:36.000125...10138.62538
392008-04-05 22:46:35.000484...3035.62539
402008-04-18 17:34:22.000890...10049.84440
412008-04-18 21:10:19.000795...3055.82841
422008-04-18 22:53:58.000343...10181.28142
432008-04-19 02:29:09...3430.81343
442008-04-19 04:12:06.000343...10159.37544
452008-04-19 07:47:15.000702...3351.01645
462008-04-19 09:29:52.000703...9911.54746
472008-04-19 13:01:42.000561...3154.87547
482008-04-19 14:43:41.000265...10250.28248
492008-04-19 18:27:29.000827...2687.71949
502008-04-19 20:09:47.000750...9695.26550
512008-04-19 23:41:55.000686...3233.0779999999951
522008-04-20 01:22:53.000953...10018.81252
532008-04-20 04:54:41.000734...3390.92253
542008-04-20 06:35:39.000765...10265.53154
552008-04-20 10:16:28.000859...3392.2555
562008-04-20 11:57:08.000765...10192.68856
572008-04-20 15:37:05.000280...3410.68857
582008-04-20 17:17:44.000030...9938.35958
592008-04-20 20:50:38.000920...2938.15659
602008-04-20 22:31:18.000045...10660.57860
612008-04-21 02:15:02.000921...3076.76661
622008-04-21 17:51:26.000312...10361.01662
632008-04-22 14:15:41.000186...1671.7563
642008-04-22 15:33:49.000875...3422.12564
652008-04-22 17:04:53.000218...10039.76665
662008-04-22 20:26:18.000920...3214.18766
672008-04-22 21:56:21.000405...10129.65667
682008-04-23 01:18:11.000795...3110.96968
692008-04-23 02:47:55.000453...10618.84469
702008-04-23 06:18:19.000920...3337.21970
712008-04-23 07:47:54.000718...9917.28271
722008-04-23 11:08:00.000312...3329.1472
732008-04-23 12:37:17.000515...10456.56273
742008-04-23 16:06:25.000170...3338.92274
752008-04-23 17:35:42.000828...10344.03275
762008-04-23 21:02:58.000295...3185.87576
772008-04-23 22:32:06.000671...9949.1720000000177
782008-04-24 01:51:33.000890...3329.73578
792008-04-24 03:20:23.000359...10539.40679
802008-04-24 06:50:00.000031...2998.87580
812008-04-24 08:18:41.000703...10629.65781
822008-04-24 11:49:24.000280...3224.37582
832008-04-24 13:17:56.000250...10482.8983
842008-04-24 16:45:56.000015...3312.03284
852008-04-24 18:14:28.000937...10233.10985
862008-04-24 21:38:11.000077...3263.37586
872008-04-24 23:06:34.000155...10111.48487
882008-04-25 12:03:44.000093...3148.85988
892008-04-25 17:02:35.000703...10804.92289
902008-04-25 20:03:55.000920...3273.34390
912008-04-25 21:32:20.000468...10758.98591
922008-04-26 01:04:35.000718...2998.23492
932008-04-26 02:32:41.000093...10257.32893
942008-04-26 05:57:35.000140...3227.03194
952008-04-26 07:25:23.000030...10751.06295
962008-04-26 10:58:25.000843...3168.43796
972008-04-28 17:20:47.000859...9940.90797
982008-04-29 12:15:56.000953...3328.93798
992008-04-29 13:44:46.000545...10060.81399
1002008-04-29 17:05:27.000780...3045.407100

We can see from the “duration” column that charging seems to take a longer time than discharging.

Let’s visualize this trend with an animated graph.

cycling_info.animated_bar(
    ts = "start_time",
    columns = ["type", "cycle_duration"],
)

The animated graph below shows how the cycles change throughout time. Another way we can verify that charging cycles are longer than discharging cycles is by looking at the average duration of each type of cycle.

cycling_info.bar(
    ["type"],
    method = "avg",
    of = "cycle_duration",
)

In general, charging cycles are longer than discharging cycles.

Let’s examine how voltage changes between cycles and their transitions.

cycling_info = cycling_info.groupby(
    "type",
    [
        "MIN(first_voltage_measured) AS min_first_voltage",
        "AVG(first_voltage_measured) AS avg_first_voltage",
        "MAX(first_voltage_measured) AS max_first_voltage",
        "MIN(last_voltage_measured)  AS min_last_voltage",
        "AVG(last_voltage_measured)  AS avg_last_voltage",
        "MAX(last_voltage_measured)  AS max_last_voltage",
    ],
)
cycling_info.head(100)
📅
start_time
Timestamp(29)
100%
...
123
cycle_duration
Float(22)
100%
123
cycle_id
Integer
100%
12008-04-02 13:08:17.000920...7561.751
22008-04-02 15:25:41.000593...3072.8592
32008-04-02 16:37:51.000984...10516.03
42008-04-02 19:43:48.000405...3509.614
52008-04-02 20:55:40.000811...10401.9375
62008-04-03 00:01:06.000687...3651.6416
72008-04-03 01:12:38.000670...10397.897
82008-04-03 04:16:37.000375...3309.7198
92008-04-03 05:27:49.000125...10166.7189
102008-04-03 08:33:25.000702...3507.39110
112008-04-03 09:44:35.000078...10352.14111
122008-04-03 12:55:10.000686...3530.012
132008-04-03 14:06:43.000234...10683.6113
142008-04-03 17:17:16.000015...2917.35914
152008-04-03 18:28:47.000125...9667.51515
162008-04-03 21:28:14.000718...3531.7516
172008-04-03 22:38:27.000452...9901.39117
182008-04-04 01:38:15.000217...3429.04718
192008-04-04 02:48:06.000155...9370.67219
202008-04-04 05:48:08.000609...3231.2520
212008-04-04 06:58:00.000296...9491.34421
222008-04-04 09:57:19.000765...3231.2522
232008-04-04 11:06:50.000375...9969.68823
242008-04-04 15:05:59.000905...9525.2524
252008-04-04 17:56:27.000609...3491.01625
262008-04-04 19:05:19.000234...9738.6426
272008-04-04 22:01:54.000670...3310.34427
282008-04-04 23:10:25.000420...10629.8928
292008-04-05 02:20:26.000702...2744.89129
302008-04-05 03:28:57.000234...9606.45330
312008-04-05 06:25:01.000890...3114.031
322008-04-05 07:33:12.000734...9978.51632
332008-04-05 10:30:32.000311...2002.87533
342008-04-05 11:38:22.000140...9293.96834
352008-04-05 14:34:41.000468...3171.93835
362008-04-05 15:42:11.000531...9649.34436
372008-04-05 18:39:25.000217...3192.3937
382008-04-05 19:46:36.000125...10138.62538
392008-04-05 22:46:35.000484...3035.62539
402008-04-18 17:34:22.000890...10049.84440
412008-04-18 21:10:19.000795...3055.82841
422008-04-18 22:53:58.000343...10181.28142
432008-04-19 02:29:09...3430.81343
442008-04-19 04:12:06.000343...10159.37544
452008-04-19 07:47:15.000702...3351.01645
462008-04-19 09:29:52.000703...9911.54746
472008-04-19 13:01:42.000561...3154.87547
482008-04-19 14:43:41.000265...10250.28248
492008-04-19 18:27:29.000827...2687.71949
502008-04-19 20:09:47.000750...9695.26550
512008-04-19 23:41:55.000686...3233.0779999999951
522008-04-20 01:22:53.000953...10018.81252
532008-04-20 04:54:41.000734...3390.92253
542008-04-20 06:35:39.000765...10265.53154
552008-04-20 10:16:28.000859...3392.2555
562008-04-20 11:57:08.000765...10192.68856
572008-04-20 15:37:05.000280...3410.68857
582008-04-20 17:17:44.000030...9938.35958
592008-04-20 20:50:38.000920...2938.15659
602008-04-20 22:31:18.000045...10660.57860
612008-04-21 02:15:02.000921...3076.76661
622008-04-21 17:51:26.000312...10361.01662
632008-04-22 14:15:41.000186...1671.7563
642008-04-22 15:33:49.000875...3422.12564
652008-04-22 17:04:53.000218...10039.76665
662008-04-22 20:26:18.000920...3214.18766
672008-04-22 21:56:21.000405...10129.65667
682008-04-23 01:18:11.000795...3110.96968
692008-04-23 02:47:55.000453...10618.84469
702008-04-23 06:18:19.000920...3337.21970
712008-04-23 07:47:54.000718...9917.28271
722008-04-23 11:08:00.000312...3329.1472
732008-04-23 12:37:17.000515...10456.56273
742008-04-23 16:06:25.000170...3338.92274
752008-04-23 17:35:42.000828...10344.03275
762008-04-23 21:02:58.000295...3185.87576
772008-04-23 22:32:06.000671...9949.1720000000177
782008-04-24 01:51:33.000890...3329.73578
792008-04-24 03:20:23.000359...10539.40679
802008-04-24 06:50:00.000031...2998.87580
812008-04-24 08:18:41.000703...10629.65781
822008-04-24 11:49:24.000280...3224.37582
832008-04-24 13:17:56.000250...10482.8983
842008-04-24 16:45:56.000015...3312.03284
852008-04-24 18:14:28.000937...10233.10985
862008-04-24 21:38:11.000077...3263.37586
872008-04-24 23:06:34.000155...10111.48487
882008-04-25 12:03:44.000093...3148.85988
892008-04-25 17:02:35.000703...10804.92289
902008-04-25 20:03:55.000920...3273.34390
912008-04-25 21:32:20.000468...10758.98591
922008-04-26 01:04:35.000718...2998.23492
932008-04-26 02:32:41.000093...10257.32893
942008-04-26 05:57:35.000140...3227.03194
952008-04-26 07:25:23.000030...10751.06295
962008-04-26 10:58:25.000843...3168.43796
972008-04-28 17:20:47.000859...9940.90797
982008-04-29 12:15:56.000953...3328.93798
992008-04-29 13:44:46.000545...10060.81399
1002008-04-29 17:05:27.000780...3045.407100

From this table, it looks like batteries are charged until they are almost full (4.2V) and discharging doesn’t begin until they are fully charged.

End-of-life (EOL) criteria for batteries is usually defined as when the battery capacity is lower than 70%-80% of its rated capacity. Since the rated capacity by the manufacturer for this battery is 2Ah, this battery is considered EOL when its capacity reaches 2Ah x 70% = 1.4Ah.

Let’s plot the capacity curve of the battery with its smoothed version and observe when it reaches the degradation criteria.

But first we need to perform some preprocessing.

discharging_data = battery5[battery5["type"] == "discharge"]
d_cap = discharging_data[["start_time", "Capacity"]].groupby(["start_time", "Capacity"])
d_cap["discharge_id"] = "ROW_NUMBER() OVER(ORDER BY start_time, Capacity)"
d_cap.rolling(
    func = "mean",
    columns = "capacity",
    window = (-100, -1),
    name = "smooth_capacity",
)
📅
start_time
Timestamp(29)
100%
...
123
Capacity
Float(22)
100%
123
smooth_capacity
Float(22)
99%
12008-05-26 20:21:04.000921...1.28745252213794[null]
22008-05-26 15:30:43.000968...1.288003392619121.28745252213794
32008-05-26 10:44:38.000468...1.293463613844241.28772795737853
42008-05-26 01:13:02.000796...1.297887080649571.2896398428671
52008-05-26 06:01:08.000170...1.298073505691891.29170165231272
62008-05-25 10:51:37.000015...1.303032918640591.29297602298855
72008-05-25 15:37:08.000890...1.303357355903821.29465217226389
82008-05-25 20:23:04.000453...1.303410043865561.29589576992674
92008-05-25 05:57:17.000890...1.307795994694441.29683505416909
102008-05-27 15:52:41.000359...1.309015364230741.29805293644969
112008-05-25 01:06:49.000828...1.313202063444221.29914917922779
122008-05-24 20:21:13.000436...1.313475132132431.30042671415656
132008-05-22 11:23:22.000187...1.318169158844521.30151408232121
142008-05-22 20:59:50.000203...1.31829304010271.30279524205378
152008-05-22 16:17:58.000890...1.318466443664031.3039022276287
162008-05-24 15:31:10.000593...1.318633896753291.30487317536439
172008-05-22 06:32:40.000920...1.323170899227621.30573322045119
182008-05-24 10:42:50.000484...1.32367412749991.30675896626157
192008-05-23 12:13:16.000359...1.323872422244271.30769869744148
202008-05-27 20:45:42.000125...1.325079328642941.30854994611531

Now we can plot the graphs. In VerticaPy we have multiple options to plot the graphs with different syntax of customization. For a complete list of all the graphs and their options check out the Chart Gallery.

Now let’s first try to plot this using Matplotlib:

import matplotlib.pyplot as plt
from matplotlib.pyplot import axhline

# Switch the plotting library to Matplotlib
vp.set_option("plotting_lib", "matplotlib")

fig = plt.figure()
ax = d_cap.plot(ts = "discharge_id", columns = ["Capacity", "smooth_capacity"])
ax.axhline(y = 1.4, label = "End-of-life criteria")
ax.set_title("Capacity degradation curve of the battery, its smoothed version and its end-of-life threshold")
ax.legend()
plt.show()
import matplotlib.pyplot as plt

from matplotlib.pyplot import axhline

# Switch the plotting library to Matplotlib
vp.set_option("plotting_lib", "matplotlib")

fig = plt.figure()

ax = d_cap.plot(ts = "discharge_id", columns = ["Capacity", "smooth_capacity"])

ax.axhline(y = 1.4, label = "End-of-life criteria")
Out[10]: <matplotlib.lines.Line2D at 0x7fd387638b80>

ax.set_title("Capacity degradation curve of the battery, its smoothed version and its end-of-life threshold")
Out[11]: Text(0.5, 1.0, 'Capacity degradation curve of the battery, its smoothed version and its end-of-life threshold')

ax.legend()
Out[12]: <matplotlib.legend.Legend at 0x7fd3876e8670>

plt.show()
_images/examples_battery_matplotlib_capacity_degradation.png

We can now try to plot it using Plotly. We can conveniently switch between the plotting libraries using:

# Switch the plotting library to Plotly
vp.set_option("plotting_lib", "plotly")
import plotly.graph_objects as go

plot = d_cap.plot(ts = "discharge_id", columns = ["Capacity", "smooth_capacity"], title = "Capacity degradation curve of the battery, its smoothed version and its end-of-life threshold")

# Add horizontal line
plot.add_hline(y = 1.4, line_width = 3, line_dash = "dash", line_color = "green")

# Add legend for the horizontal line
plot.add_trace(go.Scatter(x = [None], y = [None], mode = "lines", line = dict(color="green", width=3, dash="dash"), name = "End-of-life criteria"))

The sudden increases in battery capacity come from the self-charging property of Li-ion batteries. The smoothed graph makes the downward trend in the battery’s capacity very clear.

An important observation here is that the battery meets the EOL criteria around the 125th cycle.

Goal and Problem Modeling

Understanding battery health is important, but at the time of writing, there’s no direct way to measure it. In our case, we’ll create a degredation model to find the relationship between a battery’s overall health and the other properties in the dataset, which includes charge and discharge cycle duration, average voltage and current, etc.

One possible definition of the battery’s overall health (“state of health” or “SoH”) is the following:

Let \(Cap_{rate}\) be the rated capacity of the battery when it’s new (2Ah in our case), and \(Cap_{actual}\) be the actual capacity of the battery at a specific time. The state of health of the battery is defined as:

\[SoH = \frac{Cap_{actual}}{Cap_{rate}} \times 100\% = \frac{1}{2}Cap_{actual}\]

Data preparation

Outliet detection

Let’s start by finding and removing the global outliers from our dataset.

battery5.outliers(
    columns = [
        "Voltage_measured",
        "Current_measured",
        "Temperature_measured","Capacity",
    ],
    name = "global_outlier",
    threshold = 4.0,
)
battery5.filter("global_outlier = 0").drop("global_outlier")

Feature engineering

Since measurements like voltage and temperature tend to differ within the different cycles, we’ll create some features that can describe those cycles.

sample_cycle = battery5[battery5["Capacity"] == "1.83514614292266"]
sample_cycle["Voltage_measured"].plot(ts = "Time")
sample_cycle["Temperature_measured"].plot(ts = "Time")

We’ll define new features that describe the minimum and maximum temperature during one cycle; the minimal voltage; and the time needed to reach minimum voltage and maximum temperature.

# filter for discharge cycles
discharging_data = battery5[battery5["type"] == "discharge"]

# define new features
discharge_cycle_metrics = discharging_data.groupby(
        columns = ["start_time"],
        expr = [
            "MIN(Temperature_measured) AS min_temp",
            "MAX(Temperature_measured) AS max_temp",
            "MIN(Voltage_measured) AS min_volt",
        ]
).join(
        discharging_data,
        how = "left",
        on = {"min_volt": "voltage_measured"},
        expr1 = ["*"],
        expr2 = ["Time AS time_to_reach_minvolt"],
).join(
        discharging_data,
        how = "left",
        on = {"max_temp": "temperature_measured"},
        expr1 = ["*"],
        expr2 = ["Time AS time_to_reach_maxtemp"],
)

# calculate values of SOH
discharging_data = discharging_data.groupby(["start_time", "Capacity"])
discharging_data["SOH"] = discharging_data["Capacity"] * 0.5

# define the final dataset and save it to db
final_df = discharge_cycle_metrics.join(
    discharging_data,
    on_interpolate = {"start_time": "start_time"},
    how = "left",
    expr1 = ["*"],
    expr2 = ["SOH AS SOH"],
)

# normalize the features
final_df.normalize(
    method = "minmax",
    columns = [
        "min_temp",
        "max_temp",
        "min_volt",
        "time_to_reach_minvolt",
        "time_to_reach_maxtemp",
    ],
)

# save it to db
final_df.to_db(name = "battery_data.finaldata_battery_5")

Machine Learning

AutoML tests several models and returns input scores for each. We can use this to find the best model for our dataset.

Note

We are only using the three algorithms, but you can change the estimator parameter to try all the native algorithms: ``estimator = ‘native’ ``

from verticapy.machine_learning.vertica.automl import AutoML
from verticapy.machine_learning.vertica import LinearRegression, RandomForestRegressor, Ridge

model = AutoML(
    "battery_data.battery_autoML",
    estimator = [
        RandomForestRegressor(),
        LinearRegression(),
        Ridge(),
    ],
    estimator_type = "regressor"
)
model.fit(
    "battery_data.finaldata_battery_5",
    X = [
        "min_temp",
        "max_temp",
        "min_volt",
        "time_to_reach_minvolt",
        "time_to_reach_maxtemp",
    ],
    y = "SOH",
)

We can visualize the performance and efficency differences of each model with a plot.

model.plot()
# take the best model and its parameters
best_model = model.best_model_

params = best_model.get_params()

print(best_model._model_type)
LinearRegression

We can now define the model using those hyperparameters and train it.

# define a regression model based on the selected parameters
model_rf = LinearRegression(name = "btr_lr1", **params)
model_rf.fit(
    final_df,
    X = [
        "min_temp",
        "max_temp",
        "min_volt",
        "time_to_reach_minvolt",
        "time_to_reach_maxtemp",
    ],
    y = "SOH",
)
model_rf.regression_report()
value
explained_variance0.96333884685611
max_error0.0826358574982834
median_absolute_error0.00811763379847186
mean_absolute_error0.0125867357193153
mean_squared_error0.000330328059204944
root_mean_squared_error0.0181749294140292
r20.96333884685611
r2_adj0.962207329783768
aic-1333.62233469026
bic-1315.84749491322

The predictive power of our model looks pretty good. Let’s use our model to predict the SoH of the battery. We can visualize our prediction with a plot against the true values.

# take the predicted values and the plot them along the true ones
result = model_rf.predict(
    final_df,
    name = "SOH_estimates",
)
result.plot(
    ts = "start_time",
    columns = ["SOH", "SOH_estimates"],
)

Conclusion

We successfully defined a battery degradation model that can make accurate predictions about the health of a Li-ion battery. This model could be used to, for example, accurately send warnings to users when their batteries meet the EOL criteria.