Obiekt ramki w Pandas na podstawie danych z pliku pdf

Wczytanie danych z pliku pdf wymaga zainstalowanego modułu tabula-py. Moduł ten umożliwia także zapis wczytanych danych do pliku z danymi w formacie csv lub json.

import tabula
df_list = tabula.read_pdf('file.pdf')

Funkcja read_pdf wczytuje domyślnie jedną stronę z pliku pdf, jeśli nie podano wartości dla parametru pages (jeśli chcemy wczytać wszystkie strony to należy wpisać parametr pages=’all’).

Powyższa funkcja zwraca obiekt listy zawierającej kolejne obiekty typu DataFrame, na przykład:

df = df_list[0]        # pierwszy obiekt ramki

Wczytanie danych z pliku csv do numpy.ndarray

Gdy w źródłowych danych nie ma brakujących wartości to możemy użyć funkcję numpy.loadtxt().

Jeśli jednak we wczytywanym pliku brak jest jakiejś wartości to zamiast powyższej funkcji możemy użyć funkcję numpy.genfromtxt() tzn.

import os
import numpy as np

script_dir = os.path.dirname(__file__)
path_to_file = os.path.join(script_dir, 'data_file.csv')

data_array = np.genfromtxt(path_to_file, dtype='str')

Funkcja genfromtxt() zwraca obiekt typu numpy.ndarray. Jako dodatkowe parametry funkcji możemy dodać np.

  • delimiter - określa jaki znak oddziela poszczególne wartości
  • skip_header - określa ile linii od początku pliku ma zostać pominiętych
  • autostrip - parametr typu bool określający czy spacje mają być automatycznie usuwane

Listę wszystkich parametrów można znaleźć tutaj.

Mapy kolorów w matplotlib

Aby utworzyć wykres punktowy, korzystający z wybranej palety kolorów należy jako argument cmap podać nazwę wybranego schematu kolorów. Kolory pogrupowane są w grupy >>dokumentacja<< – np. Sequential – różne natężenie jednego koloru, Diverging – różne natężenia dwóch kontrastowych kolorów, Qualitative – kontrastowe różne kolory, ale pasujące do palety np. Pastel1 – zawierający różne kolory pastelowe.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

ax.set_title('Tytuł wykresu')
ax.set_xlabel('Etykieta osi X')
ax.set_ylabel('Etykieta osi Y')

# x - lista z wartościami osi X np. nazwa produktu
# y - lista z wartościami osi Y np. cena produktu
# intensivity - lista z liczbami odpowiadającymi instensywności danej cechy
# cm - nazwa wybranej palety kolorów

ax.scatter(x, y, c=intensivity,  cmap=cm)  
  # Przykładowo: plt.scatter(x, y, c=intensivity, s=50,  cmap='plasma')

mappable = ax.collections[0]
cbar = fig.colorbar(mappable=mappable)
cbar.set_label('intensivity')

plt.show()

Parametr s odpowiada za wielkość rysowanych znaków.

Aby wyświetlić pasek colorbar, który obrazuje intensywność danej cechy dla wyświetlonego punktu należy jako parametr podać obiekt mappable, który dla wykresu punktowego jest przechowywany jako element listy collections obiektu AxesSubplot.

W efekcie na wykresie, który może przedstawiać np. nazwę produktu i cenę można umieścić dodatkową informację za pomocą koloru np. popularność wśród kupujących lub ilość towaru w magazynie (np. kolor zielony – towar dostępny bez problemu aż po kolor czerwony – brak towaru w magazynie)

Uruchomienie programu ze skryptu

Aby w Pythonie uruchomić inny program, możemy w tym celu użyć funkcję system() z modułu os. Problem jest w przypadku, jeśli chcemy odczytać rezultat działania uruchomionego programu, tzn.

import os
result = os.system('df -h')
print(result)

Powyższy program wyświetli w konsoli wynik działania programu df, ale wartością zmiennej result jest kod błędu wykonania funkcji os.system(). Jeżeli chcemy, aby zmienna result przechowywała rezultat zwracany przez program, to zamiast funkcji system() należy użyć funkcję popen() lub jej nowszy odpowiednik – klasę Popen z modułu subprocess. tzn.

# 1 first option - function popen()
import os
result = os.popen('df -h').read()
print(result)

# 2 second option - Popen class
import subprocess
command = subprocess.Popen('df -h', shell=True, stdout=subprocess.PIPE)
result = command.stdout.read().decode('utf-8')
print(result)

Parsowanie pliku CSV

Pliki csv są plikami tekstowymi, w których każdy wiersz reprezentuje jeden rekord danych, a poszczególne dane w wierszu są przedzielone delimiterem, najczęściej znakiem przecinka.

W poniższym przykładzie parsujemy plik raportu tankowań ze stacji paliwowej. Pierwszy wiersz jest nagłówkiem i zawiera dane: Dane Kontrahenta;Imie;Nazwisko;Numer Korekty;Numer WZ;Data;Godzina;Licznik;Stacja;Numer Rejestracyjny;Numer Karty;Nazwa towaru;VAT procent;Cena na stacji;Cena netto;Cena brutto;Wartość rabatu;Ilość;Netto;VAT;Brutto. W tym konkretnym przypadku delimiterem jest znak średnika. Kolejne wiersze będą zawierały wpisy o kolejnych tankowaniach. Chcemy uzyskać z pliku źródłowego dane o dacie tankowania, numerze rejestracyjnym auta i pobraną ilość litrów paliwa.

import csv

with open('raport.csv') as csv_file:
    csv_reader = csv.DictReader(csv_file, delimiter=';')
    total = 0
    for line in csv_reader:
        print('{}  {}  {} ltr'.format(
            line['Data'], line['Numer Rejestracyjny'], line['Ilość']))

        total += float((line['Ilość']).replace(',', '.'))
    print('Total: ', total, 'ltr')

    with open('NOWE_ZESTAWIENIE.csv', 'w') as new_csv_file:
        field_names = ['Data', 'Auto', 'Tankowanie']
        csv_writer = csv.DictWriter(
            new_csv_file, fieldnames=field_names, delimiter=';')
        csv_writer.writeheader()
        csv_file.seek(0)
        next(csv_reader)
        for line in csv_reader:
            dict = {}
            dict['Data'] = line['Data']
            dict['Auto'] = line['Numer Rejestracyjny']
            dict['Tankowanie'] = line['Ilość']
            csv_writer.writerow(dict)

Parsowanie dokonujemy przy użyciu modułu csv. Następnie używając menadżera kontekstu otwieramy do odczytu plik raport.csv ze stacji paliwowej. Do odczytu używamy obiekt DictReader, dzięki czemu będzie można odwoływać się do wartości poprzez podanie kluczy z nagłówka pliku csv. Następnie obliczona jest wartość total – całkowita ilość pobranego paliwa w okresie objętym w raporcie (poszczególne dane o tankowaniach są rzutowane na wartość float, przy czym polski zapis części dziesiętnej z przecinkiem jest zamieniany na zapis z kropką).

Pozyskane dane o tankowaniach zapisujemy w pliku NOWE_ZESTAWIENIE.csv. W tym przypadku używamy obiekt DictWriter. Aby jeszcze raz skorzystać z iteratora należy ustawić wskaźnik zawartości pliku na początek pliku – csv_file.seek(0). Domyślne nagłówki zastępujemy nowymi, zawartymi w liście field_names, dlatego konieczne jest przeskoczenie do nowej wartości iteratora poprzez wykonanie next(csv_reader). Nowy wiersz zapisujemy w pliku za pomocą metody writerow() obiektu csv.DictWriter.

PySimpleGUI – prostsze tworzenie GUI w Pythonie

PySimpleGUI jest wrapperem ułatwiającym i przyspieszającym tworzenie aplikacji okienkowych w Pythonie. Dostępne są 4 porty, bazujące na następujących bibliotekach: tkinter, Qt, WxPython, Remi (interfejs webowy!).

Zmiana portu np. z PySimpleGUI (bazującego na tkinterze) na PySimpleGUIQt (korzystającego z Qt) nie wymaga dalszych zmian w kodzie programu! Więcej szczegółów można przeczytać >>tutaj<<

Przykładowy program pobierający dane od użytkownika i wyświetlający pobrane dane w drugim oknie:

import PySimpleGUIQt as sg

layout = [[sg.Text('Wprowadź przykładowy tekst')],
          [sg.InputText()],
          [sg.Submit('Zastosuj'), sg.Cancel('Anuluj')]]

window = sg.Window('Okno pobierające dane', layout)

event, values = window.Read()

window.Close()

if event == 'Zastosuj':
    text_input = values[0]
    sg.Popup('Wprowadzony text:', text_input, title='Okno wyświetlające 
        dane')

Pierwsza linia skryptu importuje moduł wrappera (musi być zainstalowany w systemie np. przez pip, jak również framework, który jest wykorzystywany przez dany port – w tym przypadku Qt).

Kolejna linia definiuje wygląd szablonu (layout), który jest listą list. Każda kolejna lista definiuje kolejny wiersz szablonu – w pierwszym wierszu element Text – który tworzy etykietę o treści Wprowadź przykładowy tekst, w drugim pole wprowadzania InputText, a w trzecim wierszu przyciski Zastosuj i Anuluj.

Następnie tworzymy okno i przypisujemy jako drugi argument wcześniej utworzony layout.

Naciśnięcie jakiegokolwiek przycisku lub zamknięcie okna spowoduje przejście do odczytu stanu okna – zwracana jest krotka składającą się z: elementu event (np. nazwy przycisku lub None gdy naciśnięto przycisk zamykania okna) oraz słownika zawierającego klucze opisujące pola wprowadzania okna oraz wartości wprowadzone w te pola. Gdy nie podano argumentu key dla pola wprowadzania podczas tworzenia layoutu, to domyślnie kluczami słownika są kolejne wartości całkowitoliczbowe. W powyższym przykładzie wpisany tekst, który jest zapisany w słowniku values jako wartość klucza 0, jest przekazywany jako element napisu wyświetlanego w okienku popup (Okno wyświetlające dane).

Okienkowe aplikacje w Pythonie i PyQt5

W Pythonie możemy wykorzystywać pliki .ui opisujące wygląd interfejsu wygenerowanego w narzędziu Qt Designer. Aby uruchomić aplikację okienkową wykorzystującą xml-owy plik ui zawierający wygląd interfejsu i korzystającą z biblioteki Qt należy zainstalować nakładkę PyQt5 tzn.

pip install PyQt5

Następnie już możemy skorzystać z następującego kodu, który ładuje plik ui i ustawia tytuł okna aplikacji

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic

class MyApp(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        QMainWindow.setWindowTitle(self, 'App Window Title')
        uic.loadUi('app.ui', self)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

Powyższy kod jeszcze niczego nie robi, poza wyświetleniem okna z wybranymi przyciskami, etykietami itp. Aby te elementy mogły reagować np. na kliknięcie, należy zdefiniować sygnały i sloty dla elementu GUI np. dla przycisku o nazwie calculateButton tworzymy metodę, która będzie slotem odpowiadającym na sygnał clicked. Listę sygnałów, na które może odpowiadać dany element można znaleźć w dokumentacji biblioteki Qt. Przypisanie metody reagującej na sygnał umieszczamy w metodzie __init__() tzn.

self.calculateButton.clicked.connect(self.my_calculate_function)

a następnie definiujemy metodę, która będzie się uruchamiać po naciśnięciu przycisku:

def my_calculate_function(self):
        pass

Prosty konsolowy kalendarz – cz.2

We wcześniejszym >>wpisie<< konsolowy kalendarz nie wykorzystuje bogactwa modułu calendar. Aby uprościć kod można wykorzystać ‘gotowca’ czyli klasę TextCalendar z tego modułu. Dzięki temu można zdefiniować funkcję cal2(), którą można zaimportować z modułu cal2, bądź też wywołać z konsoli. Przy wywołaniu z konsoli musimy nadać plikowi prawa do wykonania (chmod +x cal2.py).

Uruchomienie pliku bez podania argumentów tzn. ./cal2.py spowoduje wyświetlenie w konsoli kalendarza dla bieżącego roku i miesiąca. Alternatywnie możemy uruchomić program z 2 argumentami: rok oraz miesiąc. Podanie innej liczby argumentów wywoła wyjątek TypeError.

#!/usr/bin/env python

import calendar

from datetime import datetime

import sys

def cal2(year=datetime.today().year, month=datetime.today().month):
    '''
    Simple console calendar.
    '''
    c = calendar
    text_calendar = c.TextCalendar(c.firstweekday())
    print(text_calendar.formatmonth(year, month))

if __name__ == '__main__':
    args = sys.argv[1:]
    if len(args) == 0:
        cal2()
    elif len(args) == 2:
        cal2(int(args[0]), int(args[1]))
    else:
        raise TypeError(
            'cal2() takes 2 arguments ({} given)'.format(len(args)))

Zamiast klasy TextCalendar możemy użyć klasy potomnej – LocaleTextCalendar i uzyskać napisy zgodne z lokalnymi ustawieniami językowymi.

Proste narzędzie do wyświetlania kalendarza

Program ten jest prostym odpowiednikiem konsolowego narzędzia cal, które wyświetla w konsoli kalendarz dla podanego roku i miesiąca.

Jeśli nie podano argumentów, domyślnie drukuje kalendarz dla bieżącego roku i miesiąca. /screenshot/

#!/usr/bin/env python

from datetime import datetime

from calendar import monthrange, weekheader, weekday

from sys import stdout


def cal(year=datetime.today().year, month=datetime.today().month):
    """
    Prints month calendar for given year and month.
    """

    this_year = year
    this_month = month

    # number of all days in month
    days_in_month = monthrange(this_year, this_month)[1]

    # which week day is the first day of month
    first_day_of_month = weekday(this_year, this_month, 1)

    # prints header of calendar (width 5)
    print(weekheader(5))

    # positioning loop
    # for proper position of the first element
    for i in range(first_day_of_month):
        stdout.write(6 * ' ')

    i = 1
    while i <= days_in_month:
        stdout.write(' {:>2}   '.format(i))
        # new line if sunday
        if weekday(this_year, this_month, i) == 6:
            print('')
        i += 1
    print('')

if __name__ == '__main__':
    cal()