Inflation#

Inflation is the rate at which the general level of prices for goods and sevices changes.

Contents. Definition and inflation indices, with examples of indices used in Italy: NIC, FOI, IPCA; components of inflation, with details of IPCA in Italy; correlation with other macroeconomic quantities; who controls inflation; origin of inflation

Hide code cell source
# Load libraries
import numpy as np
import pandas as pd

import plotly.graph_objects as go
import plotly.express as px
import plotly.io as pio

pio.renderers.default = 'iframe'  # plotly_mimetype+notebook' # or 'iframe'

import requests
from io import BytesIO
Hide code cell source
# Import data
folder = 'https://raw.githubusercontent.com/Basics2022/bbooks-financial-edu/master/code/data/'

#> Files
# FOI-prices: 2016-.../2025-...
# IPCA-weights: 2018/2025
# IPCA-prices:  2018-.../2025-...
# NIC-weights: 2018/2025
# NIC-prices:  2018-.../2025-...
filen = {
    'FOI-prices'  : folder+'monthly-FOI.xlsx',  # folder+'monthly-FOI.xlsx' ,
    'IPCA-weights': folder+'Classificazione%20Ecoicop%20(4%20cifre)%20(IT1%2C168_6_DF_DCSP_IPCA3_1%2C1.0).xlsx',        # folder+'Classificazione Ecoicop (4 cifre) (IT1,168_6_DF_DCSP_IPCA3_1,1.0).xlsx',
    'IPCA-prices' : folder+'Classificazione%20Ecoicop%20(4%20cifre)%20(IT1%2C168_760_DF_DCSP_IPCA1B2015_1%2C1.0).xlsx', #folder+'Classificazione Ecoicop (4 cifre) (IT1,168_760_DF_DCSP_IPCA1B2015_1,1.0).xlsx',
    'NIC-weights' : folder+'Classificazione%20Ecoicop%20(5%20cifre)%20(IT1%2C167_743_DF_DCSP_NIC3B2015_3%2C1.0).xlsx',  # folder+'Classificazione Ecoicop (5 cifre) (IT1,167_743_DF_DCSP_NIC3B2015_3,1.0).xlsx',
    'NIC-prices'  : folder+'Classificazione%20Ecoicop%20(5%20cifre)%20(IT1%2C167_744_DF_DCSP_NIC1B2015_4%2C1.0).xlsx' # folder+'Classificazione Ecoicop (5 cifre) (IT1,167_744_DF_DCSP_NIC1B2015_4,1.0).xlsx'
}
Hide code cell source
df_ipca = {
    'prices' : pd.read_excel(filen['IPCA-prices'], sheet_name='data', decimal=',', engine='openpyxl'),
    'weights': pd.read_excel(filen['IPCA-weights'], sheet_name='data', decimal=',', engine='openpyxl')
}

for kdf, idf in df_ipca.items():
    idf.columns = idf.columns.str.strip()  # strip whitespaces
    idf = idf.set_index(['Tempo'])
    idf.index = idf.index.str.strip()
    idf = idf.transpose()
    idf = idf.rename(columns={'index': 'Tempo'})

Inflation Indices (e.g. in Italy)#

Overall inflation is the the weighted average of inflation on different classes of goods and services, weighted for their share of expenses.

Everyone perceives its own inflation, depending on its expenses. Different indices are usually used within an economy to track inflation for some “average individual”.

Different indices may differ on values of weights, and other “details” like the effect of discounts and public transfers.

As an example, three indices are used in Italy:

  • NIC (Prezzi al Consumo per l’intera Collettività Nazionale), usually the general

  • FOI (Prezzi al Consumo per Famiglie di Operai e Impiegati), usually used for contracts, pension and inflation-linked contracts, ex-tobacco and lotteries.

  • IPCA (Indice Armonizzato dei Prezzi al Consumo, HIPC Harmonized Index of Consumer Prices), used for comparison and statistics in the EU

Hide code cell source
# Compare IPCA, NIC and FOI
# ...

Weights and Price Indices of Classes of Goods and Services - Italy IPCA#

National and International Institutions for Statistics (in Italy, ISTAT) provide open-access databases collecting statistics about society and economics, including data about price.

ISTAT. As an example, Italian ISTAT provides data at https://esploradati.istat.it/databrowser/#/it/dw

All the data we need here is available under the category “Prezzi” - Prices. In order to reach a reasonable stability of the notebook, data have been downloaded, cleaned and stored in a folder on the repository of the project.

Inspect Data#

Before producing plots, price indices and weights of level-4 categories are visually inspected. Data are usually collected in tables.

Category Price Indices - Level-4 IPCA#

Hide code cell source
df_ipca['prices']
Hide code cell output
Tempo 2018-01 2018-02 2018-03 2018-04 2018-05 2018-06 2018-07 2018-08 2018-09 ... 2024-09 2024-10 2024-11 2024-12 2025-01 2025-02 2025-03 2025-04 2025-05 2025-06
0 [00] Indice generale 100.6 100.1 102.4 102.9 103.2 103.4 102 101.8 103.5 ... 123 123.4 123.3 123.4 122.4 122.5 124.4 124.9 124.8 125
1 [01] -- prodotti alimentari e bevande analcoli... 103.9 103 103.2 103.6 104.3 103.9 103.1 103.1 103 ... 130.8 132.3 133.3 132.6 133.8 133.7 133.8 134.8 135.4 135.7
2 [011] Prodotti alimentari 104 103.2 103.4 103.7 104.5 104.2 103.2 103.2 103.2 ... 131.2 132.9 133.8 133 134.1 134 134 134.9 135.5 ..
3 [0111] Pane e cereali 101.6 100.6 101.1 101.8 101.4 102 101.7 102.1 101.3 ... 127.2 127.6 127.8 127.8 128.5 128.2 128.4 129.2 129.3 ..
4 [0112] Carni 102.9 102.4 102.6 102.9 102.7 102.8 102.8 102.7 103 ... 125.6 125.9 126.7 127 127.9 128 128.7 129.5 130.2 ..
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
145 [SERVTRANS_5DG] Servizi relativi ai trasporti ... 101.1 102.6 104.4 104.5 104.7 107 107.6 112.6 107 ... 123 123 122.8 124.3 121.8 121.5 122.9 127.1 125 ..
146 [SERVMISC_5DG] Servizi vari (dettaglio 5-digit) 99.8 100.1 100.3 100.6 101 101.1 101.3 101.3 101.4 ... 112.4 112.9 113 113 113.3 113.5 113.8 113.9 114 ..
147 [00XEFOODUNP_5DG] Componente di fondo (core in... 99.9 99.5 102.2 103 103.1 103.3 101.5 101.3 103.2 ... 117.9 118 117.8 117.9 116.1 116.1 118.2 119.7 119.9 120.4
148 [00XEFOOD_5DG] Indice generale al netto dell'e... 99.4 99 102.2 103 103.1 103.4 101.1 100.9 103.2 ... 115.8 116 115.6 115.8 113.5 113.3 115.9 117.6 117.7 118.1
149 [00XE_5DG] Indice generale esclusi energetici ... 100.4 99.9 102.4 103.2 103.4 103.6 101.7 101.5 103.3 ... 119 119.4 119.2 119.3 117.7 117.6 119.7 121.2 121.4 121.8

150 rows × 91 columns

Category Weights - Level-4 IPCA#

Hide code cell source
df_ipca['weights']
Hide code cell output
Tempo 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025
0 [00] Indice generale 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000
1 [01] -- prodotti alimentari e bevande analcoli... 175648 176326 175240 175418 173257 172583 205912 194554 181443 181801 181425
2 [011] Prodotti alimentari 162005 162805 161810 161903 159432 158644 189091 179008 166582 167112 166336
3 [0111] Pane e cereali 30036 30342 29853 29558 29717 29778 35767 33586 31994 31422 31513
4 [0112] Carni 41803 40944 40876 39914 39286 38162 45695 43159 39770 40253 40080
... ... ... ... ... ... ... ... ... ... ... ... ...
144 [1252] Assicurazione connessa all'abitazione .. .. 1102 456 469 398 525 466 470 517 487
145 [1253] Servizi assicurativi connessi alla salu... 467 584 530 560 605 720 903 781 728 748 692
146 [1254] Assicurazioni sui mezzi di trasporto 13095 13767 13191 12641 12033 12020 14649 12737 12773 13259 12486
147 [126] Servizi finanziari n.a.c. 13060 14353 13628 12614 15124 15559 17041 14343 14862 14946 15195
148 [127] Altri servizi n.a.c. 19820 19403 19046 19833 21045 21951 18066 20537 19186 19297 20422

149 rows × 12 columns

Hide code cell source
# Useful new dataframe to match weights (yearly) and prices (monthly) later
#> Extract code and label
ddf = {}

ddf = pd.DataFrame()
ddf['code' ] = df_ipca['weights']['Tempo'].str.extract(r'(\[\d+\])')
ddf['label'] = df_ipca['weights']['Tempo'].str.strip()

#> Determine parent code
def get_parent_code(code):
    if code is None or pd.isna(code):
        return None
    code_str = str(code).strip('[]')
    if len(code_str) <= 2:
        return None  # no parent
    elif len(code_str) == 3:
        return f"[{code_str[:2]}]"
    elif len(code_str) == 4:
        return f"[{code_str[:3]}]"
    else:
        return None

#> Determine value
def get_value(code):
    if code is None or pd.isna(code):
        return None
    code_str = str(code).strip('[]')
    if len(code_str) == 4:
        return int(1)
    else:
        return int(0)

#> Determine parents and values
ddf['parent'] = ddf['code'].map( get_parent_code )

years = [str(y) for y in range(2018, 2026)]  # list of year strings
for year in years:
   ddf[str(year)] = df_ipca['weights'][str(year)] / 1e4

#> Drop [00] Indice generale
ddf = ddf[ddf['code'] != '[00]']

# ddf.head(5)

Plots#

Category weights - Level-2 IPCA#

The weights assigned to IPCA (Harmonized Index of Consumer Prices) categories represent the average expenditure share of households on each category of goods and services. These weights reflect how important each category is in the consumption basket.

These weights are revised annually to account for changing consumer behavior, as one can easily realize acting on the slider of the picture below. They are the weights used in computing the overall inflation \(i\) index, as the weighted sum of inflation \(i_c\) of IPCA categories,

\[i = \sum_{c \in \text{Cat}} i_c \, w_c \ .\]
Hide code cell source
import plotly.graph_objects as go

# Create the initial figure for the first year
fig = go.Figure()

fig.add_trace(go.Sunburst(
    ids=ddf['code'],
    labels=ddf['code'],
    parents=ddf['parent'],
    values=ddf['2025'],  # initial year
    hoverinfo='label+value+text',
    branchvalues="total",  # "toatal" or "remainder"
    sort=False,
    text=ddf['label'],
    textinfo='text+percent parent'
))
fig.update_layout(title="IPCA weights - 2025")

# Create one frame per year
frames = []
for year in years:
    frames.append(go.Frame(
        data=[go.Sunburst(
            ids=ddf['code'],
            labels=ddf['code'],
            parents=ddf['parent'],
            values=ddf[year],
            branchvalues="total",  # "toatal" or "remainder"
            sort=False,
            text=ddf['label'],
            textinfo='text+percent parent'
        )],
        name=year,
        layout=go.Layout(title_text=f"IPCA weights - {year}")
    ))

fig.frames = frames

# Add slider steps for each year
steps = []
for year in years:
    steps.append(dict(
        method='animate',
        args=[[year],  # frame name
              dict(mode='immediate',
                   frame=dict(duration=500, redraw=True),
                   transition=dict(duration=300))],
        label=year
    ))

# Layout with slider
fig.update_layout(
    # width=800, height=800,
    margin=dict(t=50, l=0, r=0, b=0),
    sliders=[dict(
        active=years.index('2025'),
        currentvalue={"prefix": "Year: "},
        pad={"t": 50},
        steps=steps
    )],
)

fig.show()

Category Prices - Level-2 IPCA#

Some categories in IPCA are subject to strong seasonal effects, meaning prices follow recurring patterns during the year.

As an example:

  • Clothing and Footwear: in July–August, retailers apply seasonal discounts (saldi) in Italy and prices in IPCA do include these discounts when they are actually applied in stores, as it’s shown by seasonal July/August price drops

  • Fresh fruits and vegetables: prone to seasonal availability, leading to fluctuating prices.

  • Travel and tourism: prices rise in summer and holidays.

Seasonality can obscure underlying inflation trends: that’s why seasonally adjusted inflation is evaluated, see below.

Hide code cell source
#> Classes
class_names = ddf[ ddf['parent'].isnull() ]['label'].tolist()

df_ipca['prices']['Tempo'] = df_ipca['prices']['Tempo'].str.strip()

# Add a trace for each row (now a column in df_T)
# df_classes = df['prices'][ df['prices']['Tempo'].isin(class_names) ]
df_classes = df_ipca['prices'][df_ipca['prices']['Tempo'].str.match(r'^\[\d{2}\]')].set_index('Tempo').transpose()

# Create a figure
fig = go.Figure()

df_classes

for column in df_classes.columns:
    fig.add_trace(go.Scatter(
        x=df_classes.index,
        y=df_classes[column],
        mode='lines',
        name=column[:20]
    ))

# Customize layout
fig.update_layout(
    title='IPCA price indices - 2015:100',
    xaxis_title='Time',
    yaxis_title='Value',
    # xaxis_tickangle=-45,
    hovermode='x unified',
    # template='plotly_white',
    # height=600,
    # width=1000,
    # legend=dict(orientation="v", x=1.02, y=1)
)

fig.show()

Category Price Changes (Inflation) - Level-2 IPCA#

The 12-month inflation rate (year-on-year or YoY) compares prices in a given month to the same month the year before. It’s already less prone to seasonal effects than the month-to-month rate.

However, even YoY rates can exhibit seasonal patterns, especially in volatile components like food, energy, and clothing. In order to reduce volatility of inflation indices, it’s possible to use:

  • Core inflation, as a measure of inflation that excludes the most volatile items (e.g., unprocessed food, energy), in order to provide a smoothed measure of inflation trends.

  • Statistical filtering, and moving averages

Energy post-2022

Since 2022, prices in the energy and utility sectors have shown exceptional volatility. Different causes may have contributed, like geopolitical tensions (notably, the war in Ukraine), “liberalized” electricity/gas markets in Italy where price caps were adjusted or removed. Inflation in energy and electricity was also influenced by a base effect (e.g., very low prices in 2020–2021 followed by spikes in 2022).

Policy interventions like tax reductions and bonuses - that are not “free” -, which may or may not be reflected in consumer prices, depending on implementation.

The use of core inflation in 2022–2023 was arguable, as energy prices didn’t just spiked and reverted, but was/is quite a long-term shock (war, sanctions, market and supply restructuring,…); as energy price influences many other sectors, food price rose as well, due to input cost shocks /fretilizers, transports,…) not as a result of seasonality only. Using core inflation and excluding energy and food components masked the true cost-of living impact on households.

Hide code cell source
df_inflation = df_classes.pct_change(periods=12).dropna(how='any')   # percent

# Create a figure
fig = go.Figure()

for column in df_inflation.columns:
    fig.add_trace(go.Scatter(
        x=df_inflation.index,
        y=df_inflation[column],
        mode='lines',
        name=column[:50]
    ))

# Customize layout
fig.update_layout(
    title='IPCA inflation by categories - 12 months price difference',
    xaxis_title='Time',
    yaxis_title='Value',
    # xaxis_tickangle=-45,
    hovermode='x unified',
    # template='plotly_white',
    # height=600,
    # width=1000,
    # legend=dict(orientation="v", x=1.02, y=1)
    legend=dict(
        orientation="h",
        yanchor="top",
        y=-0.2,
        xanchor="center",
        x=0.5
    )
)

fig.show()

Category contributions to overall inflation - Level-2 IPCA#

Hide code cell source
#> Need for aligning data with different time intevals

df_inflation.index = pd.to_datetime(df_inflation.index)
df_inflation.index = df_inflation.index.to_period('M')
# df_inflation


df_weights = df_ipca['weights'][ df_ipca['weights']['Tempo'].str.match(r'^\[\d{2}\]')].set_index('Tempo').transpose() / 1e6
#> Add a row 2026, just to use the same weights for all the months of 2025
row_2026 = df_weights.loc[['2025']].copy()
row_2026.index = ['2026']
df_weights = pd.concat([df_weights, row_2026],)


df_weights.index = pd.to_datetime(df_weights.index)
df_weights = df_weights.asfreq('YS')
# # df_weights_monthly = df_weights
df_weights = df_weights.resample('MS').ffill()
df_weights.index = df_weights.index.to_period('M')

# print(df_weights.index)
# print(df_inflation.index)


common_periods = df_inflation.index.intersection(df_weights.index)
df_monthly_weights = df_weights.loc[common_periods]


# df_monthly_weights
# df_inflation


df_monthly_weights.columns = df_monthly_weights.columns.str.strip()
df_inflation.columns = df_inflation.columns.str.strip()

df_contributions = df_monthly_weights * df_inflation

df_contributions = df_contributions.drop('[00] Indice generale', axis=1)
df_contributions.index = df_contributions.index.to_timestamp()

# df_contributions
Hide code cell source
# df_plot = df_contributions.reset_index()
df_plot = df_contributions.reset_index().rename(columns={'Tempo': 'Time'})

# df_plot['Tempo'] = pd.to_datetime(df_plot['Tempo'])
df_plot.index = pd.to_datetime(df_plot.index)
df_plot

# print(df_plot.columns.to_list())
# # print(df_plot.head())
# print(df_plot.dtypes)

# Create a new DataFrame by resetting the index and renaming the index column to 'Time'
df_new = df_contributions.reset_index()

# If the old index had no name, the column will be called 'index', so rename it:
if 'index' in df_new.columns:
    df_new = df_new.rename(columns={'index': 'Time'})
else:
    # If it has a name, replace that with 'Time'
    old_name = df_contributions.index.name
    if old_name is not None and old_name in df_new.columns:
        df_new = df_new.rename(columns={old_name: 'Time'})
    else:
        # If the index has no name and no 'index' column, just add one:
        df_new['Time'] = df_contributions.index

# Now df_new has a clean 'Time' column and a fresh integer index
# print(df_new.head())

df_new
Hide code cell output
Tempo Time [01] -- prodotti alimentari e bevande analcoliche [02] -- bevande alcoliche e tabacchi [03] -- abbigliamento e calzature [04] -- abitazione, acqua, elettricità, gas e altri combustibili [05] -- mobili, articoli e servizi per la casa [06] -- servizi sanitari e spese per la salute [07] -- trasporti [08] -- comunicazioni [09] -- ricreazione, spettacoli e cultura [10] -- istruzione [11] -- servizi ricettivi e di ristorazione [12] -- altri beni e servizi
0 2019-01-01 0.000834 0.000754 -0.000187 0.004464 0.0 0.000341 0.001504 -0.001614 -0.000238 0.0 0.001366 0.001651
1 2019-02-01 0.003028 0.001132 -0.000199 0.00446 0.000154 0.00034 0.000895 -0.001936 -0.000178 0.0 0.001114 0.001749
2 2019-03-01 0.001679 0.001058 0.00023 0.00446 -0.000076 0.000255 0.001778 -0.001873 -0.00012 0.000013 0.001106 0.001636
3 2019-04-01 0.000334 0.000772 0.000148 0.003974 -0.000076 0.000297 0.00399 -0.002419 -0.00012 0.000013 0.001935 0.001916
4 2019-05-01 0.000664 0.000676 0.000222 0.003861 0.0 0.000297 0.002936 -0.002491 -0.00006 0.000013 0.00132 0.00153
... ... ... ... ... ... ... ... ... ... ... ... ... ...
73 2025-02-01 0.004449 0.00079 -0.000433 0.003935 0.000513 0.001076 -0.00013 -0.001019 0.001017 0.000292 0.003506 0.002718
74 2025-03-01 0.00473 0.001011 0.000905 0.007548 0.000448 0.001188 -0.00142 -0.000944 0.001073 0.000292 0.004101 0.002699
75 2025-04-01 0.005978 0.000567 0.000458 0.005752 0.000318 0.001146 -0.001411 -0.000976 0.00075 0.000292 0.00492 0.002683
76 2025-05-01 0.005951 0.000621 0.000516 0.004934 0.000446 0.001183 -0.003094 -0.000877 0.00053 0.000292 0.004356 0.002681
77 2025-06-01 0.006797 0.000702 0.000458 0.002547 0.000509 0.001257 -0.001422 -0.000852 0.000584 0.000292 0.004312 0.002762

78 rows × 13 columns

Hide code cell source
y_cols = [col for col in df_new.columns if col != 'Time']

df_long = df_new.melt(id_vars='Time', value_vars=y_cols, var_name='Category', value_name='Value')

fig = px.bar(
    df_long,
    x='Time', # 'x',
    y='Value',
    color='Category',
    title='Category contributions to inflation'
)

fig.update_layout(
    barmode='stack',
    legend=dict(
        orientation="h",
        yanchor="top",
        y=-0.2,
        xanchor="center",
        x=0.5
    )
) # relative')
fig.show()


# df_contributions

Correlations in macroeconomics with inflation#

Some correlations exist1 between inflation and other macroeconocmics quantitites.

  • Phillips Curve: inverse relation between inflation and unemployment (in the short-run)

  • Money supply in the long-run “Inflation is a monetary phenomenon”, M.Friedman.

Control of Inflation#

Control of inflation is one of the goals of central banks, like the FED and the ECB.

Central banks aims at controlling inflation, matching target inflation (usaully set as 2%) by means of monetary policy:

  • interest rates (cost of money)

  • non-conventional actions, like quantitative easing (QE)/tightening (QT)

A goverment may indirectly influence inflation with fiscal policy, as taxation and government spending can influence demand.

Credibility of targets, and actors through their actions and forward guidance may influence inflation as well: expectations influences inflation.

Origin of inflation#

Origin of inflation?

  • short-run, medium-run: cost-push, demand-pull, built-in (triangle model)

  • long-run: “inflation is always and everywhere a monetary phenomenon” M.Friedman


1