Seimo narių pozicijų vizualizacija pagal balsavimus

Žemiau rasite filmukus, kuriuose galima pamatyti kaip išsidėstę seimo nariai, kiekvienu laiko momentu.

Grafikuose $x$ ir $y$ ašys neturi realios prasmės, grafikų esmė yra atstumas tarp taškų. Kiekvienas taškas yra seimo narys, taško spalva nurodo kokiai frakcijai priklauso seimo narys, spalvų reikšmes galima pamatyti grafiko legendoje.

Kuo labiau taškai nutolę vienas nuo kito, tuo labiau išsiskiria seimo narių nuomonę.

Seimo narių pozicija paskaičiuota imant trijų mėnesių balsavimų duomenis. Toks trijų mėnesių langas slenkamas laike ir kiekvienas vaizdo įrašo kadras rodo sekančios dienos, trijų mėnesių pozicija. Galutiniame rezultate matyti kaip kinta seimo narių pozicija laike.

In [1]:
%matplotlib inline
In [1]:
import os
import numpy as np
import pandas as pd
from datetime import timedelta
from matplotlib import cm
from sklearn import decomposition, manifold
from matplotlib import pyplot as plt
from tqdm import tqdm
from matplotlib.ticker import NullFormatter
from math import ceil
from IPython.display import Image, YouTubeVideo

plt.style.use('default')
In [2]:
data = pd.read_csv('http://atviriduomenys.lt/data/lrs/balsavimai/balsavimai.csv.gz')
In [7]:
data.info(null_counts=True)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5530115 entries, 0 to 5530114
Data columns (total 9 columns):
data                    5530115 non-null object
laikas                  5530115 non-null object
rezultatai.p_asm_id     5530115 non-null int64
rezultatai.vardas       5530115 non-null object
rezultatai.frakcija     5499323 non-null object
rezultatai.už           1982019 non-null object
rezultatai.prieš        326457 non-null object
rezultatai.susilaikė    499121 non-null object
key                     5530115 non-null object
dtypes: int64(1), object(8)
memory usage: 379.7+ MB

Pašalinam visus įrašus, kur Seimo narys nebalsavo. Mus domina tik tie įrašai, kur Seimo narys balsavo. Įtraukiant ir tuos, kurie nebalsavo, išsikraipys statistika, kadangi iš vis nebalsavosių yra didžioji dauguma.

In [3]:
data = data.dropna(how='all', subset=['rezultatai.už', 'rezultatai.prieš', 'rezultatai.susilaikė'])

Pasitikriname ar už/prieš/susilaikė laukų reikšmės yra tvarkingos, pavyzdžiui ar nėra + (su tarpo simboliu) ir pan.

In [9]:
pd.concat([data['rezultatai.už'], data['rezultatai.prieš'], data['rezultatai.susilaikė']]).value_counts()
Out[9]:
+    2807597
dtype: int64

Matome, kad duomenys tvarkingi. Tokiu atveju verčiam už/prieš/susilaikė į skaitinę formą, nuo $-2$ (prieš) iki $2$ (už). Susilaikė verčiamas į $-1$.

In [4]:
data['vote'] = (
    (data['rezultatai.už'] == '+') * 2 +
    (data['rezultatai.susilaikė'] == '+') * -1 +
    (data['rezultatai.prieš'] == '+') * -2
)

Kadangi vienas balsavimas identifikuojamas pagal balsavimo puslapio adresą, o adrese yra balsavimo id, ištraukiam balsavimo id į atskirą stulpelį, kad skaičiavimai veiktų greičiau.

In [12]:
data.iloc[0]['key']
Out[12]:
'http://www.lrs.lt/sip/portal.show?p_r=15275&p_k=1&p_a=sale_bals&p_bals_id=-26066'
In [5]:
data['p_bals_id'] = data['key'].str.extract(r'p_bals_id=(-?\d+)', expand=False).astype(int)

Apjungiame datos ir laiko stulpelius į vieną stulpelį ir konvertuojame naują stulpelį į datos ir laiko tipą.

In [6]:
data['laikas'] = pd.to_datetime(data['data'] + ' ' + data['laikas'])
In [12]:
data.info(null_counts=True)
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2807597 entries, 0 to 5530114
Data columns (total 11 columns):
data                    2807597 non-null object
laikas                  2807597 non-null datetime64[ns]
rezultatai.p_asm_id     2807597 non-null int64
rezultatai.vardas       2807597 non-null object
rezultatai.frakcija     2799827 non-null object
rezultatai.už           1982019 non-null object
rezultatai.prieš        326457 non-null object
rezultatai.susilaikė    499121 non-null object
key                     2807597 non-null object
vote                    2807597 non-null int64
p_bals_id               2807597 non-null int64
dtypes: datetime64[ns](1), int64(3), object(7)
memory usage: 257.0+ MB

Žemiau yra bandymas vizualizuoti, kaip keičiasi partijų išsidėstymas pagal balsavimus laike. Vizualizacijai naudojamas trijų mėnesių langas, kuris slenkamas laike, kas savaitę. Iš kiekvieno lango generuojamas paveiksliukas, taikant dimensijų mažinimo algoritmą.

Išbandžiau keletą algoritmų tiek in decomposition, tiek iš manifold paketų, bet nei vienas iš jų netinka, tokio tipo vizualizacijai. Esminė problema, kad dimensijos yra klausimai, kurių reikšmė kinta, priklausomai nuo klausimo. Tai puikiai matosi ir vizualizacijoje, kadangi karts nuo karto vaizdas apsiverčia aukštyn kojom. Taip atsitinka todėl, kad frakcijos vieniems klausimams pritaria, kitiems ne. Nustatyti klausimų poliškumą nėra paprasta.

Bet kokiu atveju, šis tas gavosi.

In [7]:
def generate_frames(data, window, interval):
    if os.path.exists('frames'):
        !rm -r frames

    !mkdir frames

    since = data['laikas'].min()
    total = ceil((data['laikas'].max() - window - data['laikas'].min()) / interval)
    no_fraction = 'Be frakcijos'

    alg = decomposition.PCA(n_components=2, random_state=0)

    for i in tqdm(range(total)):
        frame = data[(since < data['laikas']) & (data['laikas'] < (since + window))]
        frame = frame.set_index(['rezultatai.frakcija', 'rezultatai.vardas', 'p_bals_id'], drop=True)['vote'].unstack().fillna(0)

        frakcijos = list(frame.index.levels[0]) + [no_fraction]

        result = pd.DataFrame(alg.fit_transform(frame), index=frame.index).reset_index()

        fig, ax = plt.subplots(figsize=(16, 9))

        result['frakcija'] = result['rezultatai.frakcija'].fillna(no_fraction)
        for label, color in zip(frakcijos, cm.jet(np.linspace(0, 1, len(frakcijos)), alpha=.4)):
            fraction = result[result['frakcija'] == label]
            if len(fraction):
                ax.scatter(fraction[0], fraction[1], s=100, color=color, label=label)

        ax.set_title(since.date().strftime('%Y-%m-%d'))
        ax.set_xlabel('')
        ax.set_ylabel('')
        ax.xaxis.set_major_formatter(NullFormatter())
        ax.yaxis.set_major_formatter(NullFormatter())
        ax.set_xlim(-40, 40)
        ax.set_ylim(-30, 30)

        plt.legend()

        fig.savefig('frames/%04d.png' % i, dpi=120)
        ax.cla()
        plt.close(fig)

        since += interval
In [11]:
generate_frames(
    data=data.sort_values('laikas'),
    window=timedelta(days=90),
    interval=timedelta(days=1),
)
100%|██████████| 237/237 [01:50<00:00,  2.18it/s]

Galiausiai visus paveiksliukus suklijuojam į vientisą vaizdo failą.

In [15]:
Image('frames/0000.png')
Out[15]:
In [12]:
!avconv -y -r 30 -i frames/%04d.png -b:v 1000k output.mp4
ffmpeg version 2.8.11-0ubuntu0.16.04.1 Copyright (c) 2000-2017 the FFmpeg developers
  built with gcc 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.4) 20160609
  configuration: --prefix=/usr --extra-version=0ubuntu0.16.04.1 --build-suffix=-ffmpeg --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --cc=cc --cxx=g++ --enable-gpl --enable-shared --disable-stripping --disable-decoder=libopenjpeg --disable-decoder=libschroedinger --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librtmp --enable-libschroedinger --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxvid --enable-libzvbi --enable-openal --enable-opengl --enable-x11grab --enable-libdc1394 --enable-libiec61883 --enable-libzmq --enable-frei0r --enable-libx264 --enable-libopencv
  libavutil      54. 31.100 / 54. 31.100
  libavcodec     56. 60.100 / 56. 60.100
  libavformat    56. 40.101 / 56. 40.101
  libavdevice    56.  4.100 / 56.  4.100
  libavfilter     5. 40.101 /  5. 40.101
  libavresample   2.  1.  0 /  2.  1.  0
  libswscale      3.  1.101 /  3.  1.101
  libswresample   1.  2.101 /  1.  2.101
  libpostproc    53.  3.100 / 53.  3.100
Input #0, image2, from 'frames/%04d.png':
  Duration: 00:00:09.48, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: png, rgba(pc), 1920x1080 [SAR 4724:4724 DAR 16:9], 25 fps, 25 tbr, 25 tbn, 25 tbc
No pixel format specified, yuv444p for H.264 encoding chosen.
Use -pix_fmt yuv420p for compatibility with outdated media players.
[libx264 @ 0x180d7c0] using SAR=1/1
[libx264 @ 0x180d7c0] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 AVX2 LZCNT BMI2
[libx264 @ 0x180d7c0] profile High 4:4:4 Predictive, level 4.0, 4:4:4 8-bit
[libx264 @ 0x180d7c0] 264 - core 148 r2643 5c65704 - H.264/MPEG-4 AVC codec - Copyleft 2003-2015 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, mp4, to 'output.mp4':
  Metadata:
    encoder         : Lavf56.40.101
    Stream #0:0: Video: h264 (libx264) ([33][0][0][0] / 0x0021), yuv444p, 1920x1080 [SAR 1:1 DAR 16:9], q=-1--1, 1000 kb/s, 30 fps, 15360 tbn, 30 tbc
    Metadata:
      encoder         : Lavc56.60.100 libx264
Stream mapping:
  Stream #0:0 -> #0:0 (png (native) -> h264 (libx264))
Press [q] to stop, [?] for help
frame=  237 fps= 27 q=-1.0 Lsize=     978kB time=00:00:07.83 bitrate=1023.2kbits/s    
video:975kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.368449%
[libx264 @ 0x180d7c0] frame I:2     Avg QP:26.25  size:  8067
[libx264 @ 0x180d7c0] frame P:61    Avg QP:29.02  size:  4689
[libx264 @ 0x180d7c0] frame B:174   Avg QP:36.54  size:  3996
[libx264 @ 0x180d7c0] consecutive B-frames:  2.1%  0.0%  0.0% 97.9%
[libx264 @ 0x180d7c0] mb I  I16..4: 14.1% 82.0%  4.0%
[libx264 @ 0x180d7c0] mb P  I16..4:  0.5%  4.2%  2.0%  P16..4:  1.6%  0.4%  0.1%  0.0%  0.0%    skip:91.3%
[libx264 @ 0x180d7c0] mb B  I16..4:  0.0%  0.8%  0.8%  B16..8:  5.8%  0.6%  0.1%  direct: 0.3%  skip:91.6%  L0:52.9% L1:46.2% BI: 1.0%
[libx264 @ 0x180d7c0] final ratefactor: 29.92
[libx264 @ 0x180d7c0] 8x8 transform intra:62.6% inter:66.0%
[libx264 @ 0x180d7c0] coded y,u,v intra: 20.0% 18.6% 15.4% inter: 0.7% 0.8% 0.7%
[libx264 @ 0x180d7c0] i16 v,h,dc,p: 71% 26%  2%  1%
[libx264 @ 0x180d7c0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 19%  9% 59%  2%  2%  2%  2%  2%  2%
[libx264 @ 0x180d7c0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 22% 16% 33%  5%  5%  5%  5%  4%  3%
[libx264 @ 0x180d7c0] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0x180d7c0] ref P L0: 44.1%  6.0% 31.2% 18.7%
[libx264 @ 0x180d7c0] ref B L0: 75.1% 18.7%  6.2%
[libx264 @ 0x180d7c0] ref B L1: 90.0% 10.0%
[libx264 @ 0x180d7c0] kb/s:1010.16
In [26]:
YouTubeVideo('Xz466FWNgMY')
Out[26]:

Kad vizualizacijos paveiksliukas būtų stabilus, bandome įtraukti tik tuos klausimus, kurių rezultatas buvo „už“.

In [8]:
data = pd.merge(
    data,
    data.groupby('p_bals_id').agg({'vote': sum}).rename(columns={'vote': 'result'}),
    how='left', left_on='p_bals_id', right_index=True,
)
In [9]:
generate_frames(
    data=data[data['result'] > 0].sort_values('laikas'),
    window=timedelta(days=90),
    interval=timedelta(days=1),
)
100%|██████████| 7086/7086 [54:49<00:00,  1.86it/s]
In [13]:
Image('frames/0000.png')
Out[13]:
In [11]:
!avconv -y -r 30 -i frames/%04d.png output_aye.mp4
ffmpeg version 2.8.11-0ubuntu0.16.04.1 Copyright (c) 2000-2017 the FFmpeg developers
  built with gcc 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.4) 20160609
  configuration: --prefix=/usr --extra-version=0ubuntu0.16.04.1 --build-suffix=-ffmpeg --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --cc=cc --cxx=g++ --enable-gpl --enable-shared --disable-stripping --disable-decoder=libopenjpeg --disable-decoder=libschroedinger --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librtmp --enable-libschroedinger --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxvid --enable-libzvbi --enable-openal --enable-opengl --enable-x11grab --enable-libdc1394 --enable-libiec61883 --enable-libzmq --enable-frei0r --enable-libx264 --enable-libopencv
  libavutil      54. 31.100 / 54. 31.100
  libavcodec     56. 60.100 / 56. 60.100
  libavformat    56. 40.101 / 56. 40.101
  libavdevice    56.  4.100 / 56.  4.100
  libavfilter     5. 40.101 /  5. 40.101
  libavresample   2.  1.  0 /  2.  1.  0
  libswscale      3.  1.101 /  3.  1.101
  libswresample   1.  2.101 /  1.  2.101
  libpostproc    53.  3.100 / 53.  3.100
Input #0, image2, from 'frames/%04d.png':
  Duration: 00:04:43.44, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: png, rgba(pc), 1920x1080 [SAR 4724:4724 DAR 16:9], 25 fps, 25 tbr, 25 tbn, 25 tbc
No pixel format specified, yuv444p for H.264 encoding chosen.
Use -pix_fmt yuv420p for compatibility with outdated media players.
[libx264 @ 0x16c7a60] using SAR=1/1
[libx264 @ 0x16c7a60] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 AVX2 LZCNT BMI2
[libx264 @ 0x16c7a60] profile High 4:4:4 Predictive, level 4.0, 4:4:4 8-bit
[libx264 @ 0x16c7a60] 264 - core 148 r2643 5c65704 - H.264/MPEG-4 AVC codec - Copyleft 2003-2015 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, mp4, to 'output_aye.mp4':
  Metadata:
    encoder         : Lavf56.40.101
    Stream #0:0: Video: h264 (libx264) ([33][0][0][0] / 0x0021), yuv444p, 1920x1080 [SAR 1:1 DAR 16:9], q=-1--1, 30 fps, 15360 tbn, 30 tbc
    Metadata:
      encoder         : Lavc56.60.100 libx264
Stream mapping:
  Stream #0:0 -> #0:0 (png (native) -> h264 (libx264))
Press [q] to stop, [?] for help
frame= 7086 fps= 28 q=-1.0 Lsize=   16256kB time=00:03:56.13 bitrate= 563.9kbits/s    
video:16172kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.520091%
[libx264 @ 0x16c7a60] frame I:29    Avg QP:16.28  size: 19498
[libx264 @ 0x16c7a60] frame P:1825  Avg QP:17.46  size:  4965
[libx264 @ 0x16c7a60] frame B:5232  Avg QP:29.32  size:  1325
[libx264 @ 0x16c7a60] consecutive B-frames:  1.4%  0.2%  0.5% 97.9%
[libx264 @ 0x16c7a60] mb I  I16..4: 22.3% 71.9%  5.8%
[libx264 @ 0x16c7a60] mb P  I16..4:  0.5%  0.9%  0.9%  P16..4:  1.4%  0.8%  0.4%  0.0%  0.0%    skip:95.2%
[libx264 @ 0x16c7a60] mb B  I16..4:  0.0%  0.1%  0.1%  B16..8:  2.5%  0.5%  0.1%  direct: 0.0%  skip:96.7%  L0:50.9% L1:48.0% BI: 1.1%
[libx264 @ 0x16c7a60] 8x8 transform intra:52.3% inter:35.3%
[libx264 @ 0x16c7a60] coded y,u,v intra: 19.0% 15.8% 13.8% inter: 0.3% 0.2% 0.2%
[libx264 @ 0x16c7a60] i16 v,h,dc,p: 84% 14%  2%  0%
[libx264 @ 0x16c7a60] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 20%  5% 73%  0%  0%  0%  0%  0%  0%
[libx264 @ 0x16c7a60] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 21% 15% 31%  5%  6%  6%  6%  5%  5%
[libx264 @ 0x16c7a60] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0x16c7a60] ref P L0: 59.3%  5.7% 21.7% 13.3%
[libx264 @ 0x16c7a60] ref B L0: 82.2% 14.6%  3.1%
[libx264 @ 0x16c7a60] ref B L1: 93.7%  6.3%
[libx264 @ 0x16c7a60] kb/s:560.85
In [12]:
YouTubeVideo('jXSnkhtvLYw')
Out[12]: