Aufgabe 7*: Operatoren der Relationalen Algebra implementieren#
Im folgenden sollen die Operatoren der relationalen Algebra für Pandas Dataframes implementiert werden.
Gehe hierbei davon aus, dass die Spalten der Dataframes benannt sind und ignoriert den Index der DataFrames.
In diesem ersten Snippet wird eine Funktion definiert, mit der du deine Lösungen später testen kannst. Du musst es dir nicht im Detail ansehen, sondern nur einmal ausführen.
import pandas as pd
from pandas import DataFrame
from typing import *
from io import StringIO
import numpy as np
df = DataFrame(
[
(11, 22, 34, 23),
(11, 22, 31, 11),
(13, 42, 16, 21),
(55, 32, 71, 22),
(66, 33, 19, 27),
(13, 42, 31, 11),
],
columns=list("abyz"),
)
df2 = DataFrame([(11, 22), (13, 42)], columns=list("ab"))
df3 = DataFrame(
[
(11, 22, 34, 23),
(15, 22, 41, 11),
(13, 42, 16, 21),
(55, 32, 71, 22),
(66, 34, 19, 27),
],
columns=list("abyz"),
)
test = [
('select("y > 30 and z <= 20", df)', "a,b,y,z\r\n11,22,31,11\r\n13,42,31,11\r\n"),
(
'project(["z", "y"], df)',
"z,y\r\n23,34\r\n11,31\r\n21,16\r\n22,71\r\n27,19\r\n11,31\r\n",
),
(
'rename(["a", "b", "c", "d"], df)',
"a,b,c,d\r\n"
"11,22,34,23\r\n"
"11,22,31,11\r\n"
"13,42,16,21\r\n"
"55,32,71,22\r\n"
"66,33,19,27\r\n"
"13,42,31,11\r\n",
),
(
"cross(df, df2)",
"L_a,L_b,y,z,R_a,R_b\r\n"
"11,22,34,23,11,22\r\n"
"11,22,34,23,13,42\r\n"
"11,22,31,11,11,22\r\n"
"11,22,31,11,13,42\r\n"
"13,42,16,21,11,22\r\n"
"13,42,16,21,13,42\r\n"
"55,32,71,22,11,22\r\n"
"55,32,71,22,13,42\r\n"
"66,33,19,27,11,22\r\n"
"66,33,19,27,13,42\r\n"
"13,42,31,11,11,22\r\n"
"13,42,31,11,13,42\r\n",
),
(
"union(df, df3)",
"a,b,y,z\r\n"
"11,22,34,23\r\n"
"11,22,31,11\r\n"
"13,42,16,21\r\n"
"55,32,71,22\r\n"
"66,33,19,27\r\n"
"13,42,31,11\r\n"
"11,22,34,23\r\n"
"15,22,41,11\r\n"
"13,42,16,21\r\n"
"55,32,71,22\r\n"
"66,34,19,27\r\n",
),
("minus(df, df3)", "a,b,y,z\r\n11,22,31,11\r\n66,33,19,27\r\n13,42,31,11\r\n"),
(
"intersect(df, df3)",
"a,b,y,z\r\n"
"11,22,34,23\r\n"
"11,22,31,11\r\n"
"13,42,16,21\r\n"
"55,32,71,22\r\n"
"66,33,19,27\r\n"
"13,42,31,11\r\n",
),
(
"join(df, df2)",
"a,b,y,z\r\n11,22,34,23\r\n11,22,31,11\r\n13,42,16,21\r\n13,42,31,11\r\n",
),
('join(df, df2, theta="z == R_b")', "L_a,L_b,y,z,R_a,R_b\r\n55,32,71,22,11,22\r\n"),
("divide(df, df2)", "y,z\r\n31,11\r\n"),
]
def validate(i):
query, result = test[i]
print(f"test {i}: {query}")
expected = pd.read_csv(StringIO(result)).reset_index(drop=True)
actual = eval(query).reset_index(drop=True)
try:
pd.testing.assert_frame_equal(expected, actual)
print("OK")
except:
print("expected")
print(expected)
print("actual")
print(actual)
raise
Aufgabe 7.1*: Basisoperatoren#
In den folgenden Teilaufgaben implementieren Sie die Basisoperatoren der relationalen Algebra.
Aufgabe 7.1.1*: Selektion (\(\sigma\))#
Hinweis: Der Prädikatstring ist ein beliebiger boolescher Ausdruck über die Attribute einer Entity. Du kannst die Funktion eval
benutzen um diesen String auszuwerten. Benutze das optionale Argument locals
um die Werte der Variablen des Ausdrucks zu übergeben. Zum Beispiel:
eval("a < 10", {"a": 3})
True
def select(predicate: str, relation: DataFrame) -> DataFrame:
# TODO: Hier code hinzufügen.
return relation
validate(0)
test 0: select("y > 30 and z <= 20", df)
expected
a b y z
0 11 22 31 11
1 13 42 31 11
actual
a b y z
0 11 22 34 23
1 11 22 31 11
2 13 42 16 21
3 55 32 71 22
4 66 33 19 27
5 13 42 31 11
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[4], line 1
----> 1 validate(0)
Cell In[1], line 105, in validate(i)
103 actual = eval(query).reset_index(drop=True)
104 try:
--> 105 pd.testing.assert_frame_equal(expected, actual)
106 print("OK")
107 except:
[... skipping hidden 1 frame]
File /usr/local/lib/python3.13/site-packages/pandas/_testing/asserters.py:614, in raise_assert_detail(obj, message, left, right, diff, first_diff, index_values)
611 if first_diff is not None:
612 msg += f"\n{first_diff}"
--> 614 raise AssertionError(msg)
AssertionError: DataFrame are different
DataFrame shape mismatch
[left]: (2, 4)
[right]: (6, 4)
Aufgabe 7.1.2*: Projektion (\(\pi\))#
def project(attributes: List[str], relation: DataFrame) -> DataFrame:
# TODO: Hier code hinzufügen.
return relation
validate(1)
Aufgabe 7.1.3*: rename (umbenennen) (\(\rho\))#
def rename(new_attribute_names: List[str], relation: DataFrame) -> DataFrame:
# TODO: Hier Code hinzufügen.
return relation
validate(2)
Aufgabe 7.1.4*: Kreuzprodukt (\(\times\))#
def cross(left: DataFrame, right: DataFrame) -> DataFrame:
# TODO: Hier Code hinzufügen.
return relation
validate(3)
Aufgabe 7.1.5*: Vereinigung ( \(\cup\))#
def union(left: DataFrame, right: DataFrame) -> DataFrame:
# TODO: Hier Code hinzufügen.
return relation
validate(4)
Aufgabe 7.1.6*: Differenz (\(-\))#
def minus(left: DataFrame, right: DataFrame) -> DataFrame:
# TODO: Hier Code hinzufügen.
return relation
validate(5)
Aufgabe 7.2*: Abgeleitete Operatoren#
Hinweis: Die abgeleiteten Operatoren heißen so, weil man sie von den Basisoperatoren ableiten kann.
Aufgabe 7.2.1*: Schnitt (\(\cap\))#
def intersect(left: DataFrame, right: DataFrame) -> DataFrame:
# TODO: Hier Code hinzufügen.
return left
validate(6)
Aufgabe 7.2.2*: Natural Join (\(\bowtie\)), Theta Join ( \(\bowtie_{\theta}\))#
Wenn das optionale Argument theta
leer ist, soll ein natural join durchgeführt werden.
def join(left: DataFrame, right: DataFrame, theta: str = "", outer: bool = False) -> DataFrame:
# TODO: Hier Code hinzufügen.
return left
validate(7)
validate(8)
Aufgabe 7.2.3*: Division (\(/\))#
def divide(left: DataFrame, right: DataFrame) -> DataFrame:
# TODO: Hier Code hinzufügen.
return left
validate(9)
Bei Bedarf: Alle Tests ausführen#
# Alle Lösungen mittels implementierter Funktionen generieren:
x = []
divide(df, df2)
for q, _ in test:
print(q)
x.append((q, eval(q).to_csv(index=False)))
from pprint import pprint
pprint(x)
# Alle Tests ausführen:
for i in range(10):
validate(i)