Dane PIT po kodach TERC

Dane PIT po kodach TERC

MF od 2022 r. oprócz corocznych statystyk z akcji PIT publikuje również od 2022 statystyki dotyczące liczby podatników, dochodów oraz podatku po formach rozliczenia wg gmin, po kodach TERC. Dane dosepne są tutaj.

Są dostepne w różnych formatach, najmniej przy tym waży Excel, który można po prostu sobie ściagnąć i zaczytać. Poniżej kilka pierwszych obserwacji. Uwaga jest taka, że jeśli na jednaj formie nie występowało w danej gminie 6 lub więcej osób, to trafiałą ona do puli zbiorczej, nieprzypisanych podatników.

In [1]:
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import geopandas as gpd
from pysal.lib import weights
from pysal.explore import esda
from splot.esda import plot_moran

import os

# Project directory
pDir = "~/tcsrv/projects/data/pit_terc/"
mapDir = "~/tcsrv/data/mapy/prg_2023/"
/home/mateusz/.local/lib/python3.9/site-packages/spaghetti/network.py:40: FutureWarning: The next major release of pysal/spaghetti (2.0.0) will drop support for all ``libpysal.cg`` geometries. This change is a first step in refactoring ``spaghetti`` that is expected to result in dramatically reduced runtimes for network instantiation and operations. Users currently requiring network and point pattern input as ``libpysal.cg`` geometries should prepare for this simply by converting to ``shapely`` geometries.
  warnings.warn(dep_msg, FutureWarning, stacklevel=1)
/home/mateusz/.local/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
In [2]:
pit_2023 = pd.read_excel(
    os.path.join(pDir, "dane", "pit_2023.xlsx"),
    dtype={'Kod województwa':str, 'Kod powiatu': str, 'Kod gminy': str}
)

# Dodaj 0 do kodów terc - przyda się przy tworzeniu kodów do merge
pit_2023['Kod województwa'] = pit_2023['Kod województwa'].str.pad(width=2,side='left',fillchar='0')
pit_2023['Kod powiatu'] = pit_2023['Kod powiatu'].str.pad(width=2,side='left',fillchar='0')
pit_2023['Kod gminy'] = pit_2023['Kod gminy'].str.pad(width=3,side='left',fillchar='0')


pit_2023.head()
Out[2]:
Kod województwa Kod powiatu Kod gminy Województwo Powiat Gmina Typ gminy Nazwa formularza Liczba podatników Kwota przychodu w tys. złotych Kwota dochodu w tys. złotych Kwota dochodu do opodatkowania w tys. złotych Podatek należny w tys. złotych
0 02 01 011 DOLNOŚLĄSKIE BOLESŁAWIECKI BOLESŁAWIEC GM PIT-28 2267 267601 0 0 19181
1 02 01 011 DOLNOŚLĄSKIE BOLESŁAWIECKI BOLESŁAWIEC GM PIT-36 2294 314881 138634 125672 9056
2 02 01 011 DOLNOŚLĄSKIE BOLESŁAWIECKI BOLESŁAWIEC GM PIT36L 565 1409115 145712 139967 26595
3 02 01 011 DOLNOŚLĄSKIE BOLESŁAWIECKI BOLESŁAWIEC GM PIT-37 21068 1244829 1203465 1098450 68067
4 02 01 011 DOLNOŚLĄSKIE BOLESŁAWIECKI BOLESŁAWIEC GM PIT-38 410 37279 3956 3737 695

W tabeli poniżej kilka podsumowań.

Łącznie dane zwierają informacje dla ok. 28,6 mln deklaracji. Może się to wydawać więcej niż jest podatnikó, ale to wynika z faktu, że jedna osoba może różne deklaracje składać, np. rozliczać dochody z pracy na skali podatkowej a dochody z wynajmu np. ryczałtem.

Przychody oczywiście nie obejmują dla działalności gospodarczej (DG) kosztów. Stąd lepiej posługiwać się kategorią dochodu, która też nie jest do końca idealna, ponieważ w przypadku umowy o pracę (UoP) zawiera także ryczałtowe koszty uzyskania przychodu, które to kosztami nie są, ale obniżają podatek.

Kwoty podane w kolumnach są w tys. zł, ale dla czytelności przeliczone na mld zł.

Czyli łącznie mamy 1 632 mld zł dochodów od których odprowadzono 143,9 mld zł podatku PIT (dla ryczałtu brak info o kosztach, więc dochód == 0).

In [3]:
pit_2023.iloc[:, 8:13].sum() / 1e6
Out[3]:
Liczba podatników                                      28.599857
Kwota przychodu                   w tys. złotych     3481.892689
Kwota dochodu w tys. złotych                         1632.337445
 Kwota dochodu do opodatkowania w tys. złotych       1480.481245
 Podatek należny                  w tys. złotych      143.913703
dtype: float64

Naturalne jest, że grupą zarabiającą najwięcej są liniowcy ze średnim dochodem ponad 300 tys. rocznie, co widać w poniższych podsumowaniach. To tutaj znajdziemy osoby o najwyższych dochodach z działalności. Dla porównania osoby uzyskujące dochody z działalności na skali podatkowej miały w 2023 r. średnie dochody na poziomie trochę ponad 66 tys. zł. Jest to zbliżone do dochodów ze skali podatkowej poza działalnością (ponad 55 tys. zł rocznie).

Co ciekawe grupą o najniższych średnichdochodach byli podatnicy rozliczający dochody z giełdy. No ale pamiętć należy, że patrzymy na średnią a nie na cały rozkład.

In [34]:
po_formach_2023 = pit_2023.iloc[:, 7:13].groupby("Nazwa formularza").sum() / 1e6
po_formach_2023
Out[34]:
Liczba podatników Kwota przychodu w tys. złotych Kwota dochodu w tys. złotych Kwota dochodu do opodatkowania w tys. złotych Podatek należny w tys. złotych
Nazwa formularza
PIT-28 1.987999 275.766695 0.000000 0.000000 22.122390
PIT-36 2.807064 472.304954 186.183120 168.264132 13.038433
PIT-37 21.704732 1253.190380 1199.925008 1083.304198 72.206467
PIT-38 0.466623 148.042243 16.019599 15.246831 2.870120
PIT-39 0.072931 18.783958 9.445025 0.921377 0.174398
PIT28S 0.000427 0.143055 0.000000 0.000000 0.008029
PIT36L 0.559975 1259.088325 170.650782 162.643386 30.896424
PIT36S 0.001532 0.645898 0.056171 0.051880 0.005338
PIT40A 0.997064 49.545672 49.545672 49.545646 2.496381
PT36LS 0.001510 4.381509 0.512068 0.503795 0.095723

Ciekawe jest też, że średnie dochody osób na skali podatkowej (PIT-37 i PIT-36) urosły między 2022 a 2022 mocniej niż na podatku liniowym (PIT-36) i podatku od zysku z giełdy (PIT-38). Do tego na ryczałcie sredni przychoód też wzrósł o wiele więcej niż na podatku liniowym.

In [48]:
# Ładuj dane za 2022 r.
pit_2022 = pd.read_excel(
    os.path.join(pDir, "dane", "pit_2022.xlsx")
)
po_formach_2022 = pit_2022.iloc[:, 7:13].groupby("Nazwa formularza").sum() / 1e6

# Licz średnie
sredni_dochod_23 = po_formach_2023.iloc[:, 2] / po_formach_2023.iloc[:, 0] * 1000
sredni_dochod_23.iloc[0] = \
    po_formach_2023.iloc[po_formach_2023.index == 'PIT-28', 1] / po_formach_2023.iloc[po_formach_2023.index == 'PIT-28', 0] * 1000

sredni_dochod_22 = po_formach_2022.iloc[:, 2] / po_formach_2022.iloc[:, 0] * 1000
sredni_dochod_22.iloc[0] = \
    po_formach_2022.iloc[po_formach_2022.index == 'PIT-28', 1] / po_formach_2022.iloc[po_formach_2022.index == 'PIT-28', 0] * 1000

# Do tabeli
po_formach_22_23 = pd.concat([sredni_dochod_22, sredni_dochod_23], axis=1)
po_formach_22_23.columns = ['Średnia 2022', 'Średnia 2023']
po_formach_22_23['Zmiana r/r %'] = po_formach_22_23['Średnia 2023'] / po_formach_22_23['Średnia 2022'] * 100 - 100

po_formach_22_23.style \
  .format(precision=1, thousands=" ", decimal=",") 
Out[48]:
  Średnia 2022 Średnia 2023 Zmiana r/r %
Nazwa formularza      
PIT-28 128 021,0 138 715,7 8,4
PIT-36 60 053,1 66 326,6 10,4
PIT-37 51 070,5 55 284,0 8,3
PIT-38 35 162,1 34 330,9 -2,4
PIT-39 125 257,7 129 506,3 3,4
PIT28S 0,0 0,0 nan
PIT36L 317 864,7 304 747,1 -4,1
PIT36S 33 426,9 36 665,1 9,7
PIT40A 20 252,9 49 691,6 145,4
PT36LS 362 821,8 339 117,9 -6,5

Mapy

Oczywiście grzechem by było nie skorzystać z możliwosci wizualizacji tych na danych na mapach. Tutaj nieodżałowaną pomocą jest Państwowy Rejestr Granic, gdzie bez problemu można ściągnać pliki SHP do mapek.

In [6]:
try:
    pl_gminy = gpd.read_file(os.path.join(mapDir, 'A03_Granice_gmin.shp'))
    woj = gpd.read_file(os.path.join(mapDir, 'A01_Granice_wojewodztw.shp'))
    print(pl_gminy.columns)
except ValueError as e:
    print(e)
Index(['JPT_SJR_KO', 'JPT_POWIER', 'JPT_KOD_JE', 'JPT_NAZWA_', 'JPT_ORGAN_',
       'JPT_JOR_ID', 'WERSJA_OD', 'WERSJA_DO', 'WAZNY_OD', 'WAZNY_DO',
       'JPT_KOD__1', 'JPT_NAZWA1', 'JPT_ORGAN1', 'JPT_WAZNA_', 'ID_BUFORA_',
       'ID_BUFORA1', 'ID_TECHNIC', 'IIP_PRZEST', 'IIP_IDENTY', 'IIP_WERSJA',
       'JPT_KJ_IIP', 'JPT_KJ_I_1', 'JPT_KJ_I_2', 'JPT_OPIS', 'JPT_SPS_KO',
       'ID_BUFOR_1', 'JPT_ID', 'JPT_POWI_1', 'JPT_KJ_I_3', 'JPT_GEOMET',
       'JPT_GEOM_1', 'Shape_Leng', 'Shape_Area', 'REGON', 'geometry'],
      dtype='object')
In [7]:
pl_gminy.head()
Out[7]:
JPT_SJR_KO JPT_POWIER JPT_KOD_JE JPT_NAZWA_ JPT_ORGAN_ JPT_JOR_ID WERSJA_OD WERSJA_DO WAZNY_OD WAZNY_DO ... ID_BUFOR_1 JPT_ID JPT_POWI_1 JPT_KJ_I_3 JPT_GEOMET JPT_GEOM_1 Shape_Leng Shape_Area REGON geometry
0 GMI 19711.0 0204043 Wąsosz None 13359 2012-09-26 NaT 2012-09-26 NaT ... 0 827610 0.0 None 0.0 0.0 0.966563 0.025551 45066977200000 POLYGON ((16.76417 51.48541, 16.76406 51.48559...
1 GMI 1329.0 0618011 Tomaszów Lubelski None 13174 2012-09-26 NaT 2012-09-26 NaT ... 0 828335 0.0 None 0.0 0.0 0.290408 0.001683 95036908900000 POLYGON ((23.42314 50.42633, 23.42312 50.42637...
2 GMI 9430.0 0618072 Rachanie None 13174 2012-09-26 NaT 2012-09-26 NaT ... 0 828336 0.0 None 0.0 0.0 0.715575 0.011960 95036896000000 POLYGON ((23.55155 50.4966, 23.54966 50.49668,...
3 GMI 11773.0 1410012 Huszlew None 13237 2012-09-26 NaT 2012-09-26 NaT ... 0 828830 0.0 None 0.0 0.0 0.842567 0.015447 03023752300000 POLYGON ((22.90219 52.06353, 22.90214 52.06353...
4 GMI 12129.0 1410023 Łosice None 13237 2012-09-26 NaT 2012-09-26 NaT ... 0 828832 0.0 None 0.0 0.0 0.800603 0.015951 03023740500000 POLYGON ((22.77329 52.15228, 22.77323 52.15231...

5 rows × 35 columns

In [8]:
fig, ax = plt.subplots(1, figsize=(14, 14))
pl_gminy.plot(ax=ax, linewidth=0.5, edgecolor='0.4', color='0.8')
ax.axis('off')
Out[8]:
(np.float64(13.621739949250056),
 np.float64(24.646927985750054),
 np.float64(48.710324559200075),
 np.float64(55.128135338800035))
No description has been provided for this image
In [45]:
# Dodaj kod TERC
pit_2023['terc'] = pit_2023['Kod województwa'] + pit_2023['Kod powiatu'] + pit_2023['Kod gminy']
pit_2023_mapa = pl_gminy.merge(pit_2023, left_on='JPT_KOD_JE', right_on='terc', how='left')

pit_2023_mapa['sredni_doch'] = pit_2023_mapa['Kwota dochodu w tys. złotych'] / pit_2023_mapa['Liczba podatników']
pit_2023_mapa['sredni_przychod'] = pit_2023_mapa['Kwota przychodu                   w tys. złotych '] / pit_2023_mapa['Liczba podatników']

pit_2023_mapa.head()
Out[45]:
JPT_SJR_KO JPT_POWIER JPT_KOD_JE JPT_NAZWA_ JPT_ORGAN_ JPT_JOR_ID WERSJA_OD WERSJA_DO WAZNY_OD WAZNY_DO ... Typ gminy Nazwa formularza Liczba podatników Kwota przychodu w tys. złotych Kwota dochodu w tys. złotych Kwota dochodu do opodatkowania w tys. złotych Podatek należny w tys. złotych terc sredni_doch sredni_przychod
0 GMI 19711.0 0204043 Wąsosz None 13359 2012-09-26 NaT 2012-09-26 NaT ... MW PIT-28 324.0 49741.0 0.0 0.0 2926.0 0204043 0.000000 153.521605
1 GMI 19711.0 0204043 Wąsosz None 13359 2012-09-26 NaT 2012-09-26 NaT ... MW PIT-36 409.0 68935.0 22279.0 20393.0 1053.0 0204043 54.471883 168.545232
2 GMI 19711.0 0204043 Wąsosz None 13359 2012-09-26 NaT 2012-09-26 NaT ... MW PIT36L 61.0 138994.0 20646.0 20083.0 3816.0 0204043 338.459016 2278.590164
3 GMI 19711.0 0204043 Wąsosz None 13359 2012-09-26 NaT 2012-09-26 NaT ... MW PIT-37 3733.0 175299.0 168858.0 152467.0 6925.0 0204043 45.233860 46.959282
4 GMI 19711.0 0204043 Wąsosz None 13359 2012-09-26 NaT 2012-09-26 NaT ... MW PIT-38 41.0 8365.0 148.0 145.0 28.0 0204043 3.609756 204.024390

5 rows × 51 columns

In [41]:
fig, ax = plt.subplots(1, figsize=(12, 12))
woj.plot(ax=ax, edgecolor='0', color='0')
pit_2023_mapa[pit_2023_mapa['Nazwa formularza'] == 'PIT-37'].plot(
    ax=ax, 
    column='sredni_doch', 
    cmap='Spectral_r', 
    legend=True, 
    scheme='quantiles', 
    k=10,
    legend_kwds={'loc': 'lower left'}  # This line positions the legend in the bottom left
)
ax.set_title('Średni dochód na podatnika: PIT-37', fontdict={'fontsize': 20}, loc='left')
ax.axis('off')
Out[41]:
(np.float64(13.621739949250056),
 np.float64(24.646927985750054),
 np.float64(48.710324559200075),
 np.float64(55.128135338800035))
No description has been provided for this image
In [42]:
fig, ax = plt.subplots(1, figsize=(12, 12))
woj.plot(ax=ax, edgecolor='0', color='0')
pit_2023_mapa[pit_2023_mapa['Nazwa formularza'] == 'PIT-36'].plot(
    ax=ax, 
    column='sredni_doch', 
    cmap='Spectral_r', 
    legend=True, 
    scheme='quantiles', 
    k=10,
    legend_kwds={'loc': 'lower left'}  # This line positions the legend in the bottom left
)
ax.set_title('Średni dochód na podatnika: PIT-36', fontdict={'fontsize': 20}, loc='left')
ax.axis('off')
Out[42]:
(np.float64(13.621739949250056),
 np.float64(24.646927985750054),
 np.float64(48.710324559200075),
 np.float64(55.128135338800035))
No description has been provided for this image
In [43]:
fig, ax = plt.subplots(1, figsize=(12, 12))
woj.plot(ax=ax, edgecolor='0', color='0')
pit_2023_mapa[pit_2023_mapa['Nazwa formularza'] == 'PIT36L'].plot(
    ax=ax, 
    column='sredni_doch', 
    cmap='Spectral_r', 
    legend=True, 
    scheme='quantiles', 
    k=10,
    legend_kwds={'loc': 'lower left'}  # This line positions the legend in the bottom left
)
ax.set_title('Średni dochód na podatnika: PIT-36L', fontdict={'fontsize': 20}, loc='left')
ax.axis('off')
Out[43]:
(np.float64(13.621739949250056),
 np.float64(24.646927985750054),
 np.float64(48.710324559200075),
 np.float64(55.128135338800035))
No description has been provided for this image
In [46]:
fig, ax = plt.subplots(1, figsize=(12, 12))
woj.plot(ax=ax, edgecolor='0', color='0')
pit_2023_mapa[pit_2023_mapa['Nazwa formularza'] == 'PIT-28'].plot(
    ax=ax, 
    column='sredni_przychod', 
    cmap='Spectral_r', 
    legend=True, 
    scheme='quantiles', 
    k=10,
    legend_kwds={'loc': 'lower left'}  # This line positions the legend in the bottom left
)
ax.set_title('Średni dochód na podatnika: PIT-28', fontdict={'fontsize': 20}, loc='left')
ax.axis('off')
Out[46]:
(np.float64(13.621739949250056),
 np.float64(24.646927985750054),
 np.float64(48.710324559200075),
 np.float64(55.128135338800035))
No description has been provided for this image

Analiza

No dobrze, obrazki pracudnej urody, ale wydaje się, że dochody zarówno z DG na skali jak i pozostałe (PIT-36 i PIT-37) są skoncentrowane wokół miast, natomiast średnie dochody z podatku liniowego i przychody z ryczałtu wydają się bardziej rozproszone.

Do weryfikacji tego może warto użyć bardziej zaawansowanej analityki niż tylko kolorowe mapki.

Bardzo ciekawy materiał odnośnie analiz przestrzennych w Pythonie dostępny jest tutaj.

Zacznę od stworzenia wag do obliczenia współczynnika autokorelacji Maran's I.

In [49]:
# Formularze
forms = ['PIT-37', 'PIT-36', 'PIT36L', 'PIT-28']

# Stwórz wagi i policz Moran I
moranI = {}
for form in forms:
    df = pit_2023_mapa[pit_2023_mapa['Nazwa formularza'] == form]
    
    # Wagi
    w = weights.KNN.from_dataframe(df, k=8)
    w.transform = "R"  # Row-standardize the weights
    
    # Moran's I
    if form == 'PIT-28':
        moranI[form] = esda.moran.Moran(df['sredni_przychod'], w)
    else:
        moranI[form] = esda.moran.Moran(df['sredni_doch'], w)

No i ostatecznie sam współczynnik. Potwierdza się intuicja. Dla linii i ryczałtu autokorelacja jest bliżej 0, co oznacza, że średnie wielkości dochodów tych podatników są od siebie mniej przestrzennie zależne niż dla dochodów ze skali.

In [50]:
# Print results
for key, value in moranI.items():
    print(f"{key}: {value.I:.3f}, pSim: {value.p_sim:.3f}")
PIT-37: 0.647, pSim: 0.001
PIT-36: 0.577, pSim: 0.001
PIT36L: 0.068, pSim: 0.001
PIT-28: 0.198, pSim: 0.001
In [51]:
plot_moran(moranI['PIT-36'])
Out[51]:
(<Figure size 1000x400 with 2 Axes>,
 array([<Axes: title={'center': 'Reference Distribution'}, xlabel='Moran I: 0.58', ylabel='Density'>,
        <Axes: title={'center': 'Moran Scatterplot (0.58)'}, xlabel='Attribute', ylabel='Spatial Lag'>],
       dtype=object))
No description has been provided for this image
In [17]:
plot_moran(moranI['PIT-36L'])
Out[17]:
(<Figure size 1000x400 with 2 Axes>,
 array([<Axes: title={'center': 'Reference Distribution'}, xlabel='Moran I: 0.07', ylabel='Density'>,
        <Axes: title={'center': 'Moran Scatterplot (0.07)'}, xlabel='Attribute', ylabel='Spatial Lag'>],
       dtype=object))
No description has been provided for this image
In [18]:
plot_moran(moranI['PIT-28'])
Out[18]:
(<Figure size 1000x400 with 2 Axes>,
 array([<Axes: title={'center': 'Reference Distribution'}, xlabel='Moran I: 0.2', ylabel='Density'>,
        <Axes: title={'center': 'Moran Scatterplot (0.2)'}, xlabel='Attribute', ylabel='Spatial Lag'>],
       dtype=object))
No description has been provided for this image