119 lines
7.5 KiB
Python
119 lines
7.5 KiB
Python
import math
|
||
import pandas as pd
|
||
from functools import reduce
|
||
|
||
from LabWork01.FuncLoad import createDataFrame
|
||
from LabWork01.LabWork6.ConvertorDataFrame import covertorDataFrame
|
||
|
||
# Дата сет
|
||
# data = [
|
||
# ["красный", "квадрат", "красный квадрат"],
|
||
# ["красный", "прямоугольник", "красный прямоугольник"],
|
||
# ["красный", "круг", "красный круг"],
|
||
# ["красный", "треугольник", "красный треугольник"],
|
||
#
|
||
# ["зеленый", "квадрат", "зеленый квадрат"],
|
||
# ["зеленый", "треугольник", "зеленый треугольник"],
|
||
# ["зеленый", "круг", "зеленый круг"],
|
||
# ]
|
||
# df0 = pd.DataFrame(data)
|
||
# df0.columns = ["цвет", "форма", "результат"]
|
||
|
||
df0 = covertorDataFrame()
|
||
|
||
# Лямбда-выражение для распределения значений, аргумент - pandas.Series,
|
||
# возвращаемое значение - массив с количеством каждого из значений
|
||
# Из вводных данных s с помощью value_counts() находим частоту каждого из значений,
|
||
# и пока в нашем словаре есть элементы, будет работать цикл, запускаемый items().
|
||
# Чтобы выходные данные не менялись с каждым запуском цикла, мы используем sorted,
|
||
# который меняет порядок от большего к меньшему
|
||
# В итоге, генерируется массив, содержащий строку из следующих компонентов: ключ (k) и значение (v).
|
||
cstr = lambda s: [k + ":" + str(v) for k, v in sorted(s.value_counts().items())]
|
||
|
||
# Структура данных Decision Tree
|
||
tree = {
|
||
# name: Название этого нода (узла)
|
||
"name": "decision tree " + df0.columns[-1] + " " + str(cstr(df0.iloc[:, -1])),
|
||
# df: Данные, связанные с этим нодом (узлом)
|
||
"df": df0,
|
||
# edges: Список ребер (ветвей), выходящих из этого узла,
|
||
# или пустой массив, если ниже нет листового узла.
|
||
"edges": [],
|
||
}
|
||
|
||
# Генерацию дерева, у узлов которого могут быть ветви, сохраняем в open
|
||
open = [tree]
|
||
|
||
# Лямба-выражение для вычесления энтропии.
|
||
# Аргумент - pandas.Series、возвращаемое значение - число энтропии
|
||
entropy = lambda s: -reduce(lambda x, y: x + y, map(lambda x: (x / len(s)) * math.log2(x / len(s)), s.value_counts()))
|
||
|
||
# Зацикливаем, пока open не станет пустым
|
||
while (len(open) != 0):
|
||
# Вытаскиваем из массива open первый элемент,
|
||
# и вытаскиваем данные, хранящиеся в этом узле
|
||
n = open.pop(0)
|
||
df_n = n["df"]
|
||
|
||
# В случае, если энтропия этого узла равна 0, мы больше не можем вырастить из него новые ветви
|
||
# поэтому прекращаем ветвление от этого узла
|
||
if 0 == entropy(df_n.iloc[:, -1]):
|
||
continue
|
||
# Создаем переменную, в которую будем сохранять список значений атрибута с возможностью разветвления
|
||
attrs = {}
|
||
# Исследуем все атрибуты, кроме последнего столбца класса атрибутов
|
||
for attr in df_n.columns[:-1]:
|
||
# Создаем переменную, которая хранит значение энтропии при ветвлении с этим атрибутом,
|
||
# данные после разветвления и значение атрибута, который разветвляется.
|
||
attrs[attr] = {"entropy": 0, "dfs": [], "values": []}
|
||
# Исследуем все возможные значения этого атрибута.
|
||
# Кроме того, sorted предназначен для предотвращения изменения порядка массива,
|
||
# из которого были удалены повторяющиеся значения атрибутов, при каждом его выполнении.
|
||
for value in sorted(set(df_n[attr])):
|
||
# Фильтруем данные по значению атрибута
|
||
df_m = df_n.query(attr + "=='" + value + "'")
|
||
# Высчитываем энтропию, данные и значения сохрнаяем
|
||
attrs[attr]["entropy"] += entropy(df_m.iloc[:, -1]) * df_m.shape[0] / df_n.shape[0]
|
||
attrs[attr]["dfs"] += [df_m]
|
||
attrs[attr]["values"] += [value]
|
||
pass
|
||
pass
|
||
# Если не осталось ни одного атрибута, значение которого можно разделить,
|
||
# прерываем исследование этого узла.
|
||
if len(attrs) == 0:
|
||
continue
|
||
# Получаем атрибут с наименьшим значением энтропии
|
||
attr = min(attrs, key=lambda x: attrs[x]["entropy"])
|
||
# Добавляем каждое значение разветвленного атрибута
|
||
# и данные, полученные после разветвления, в наше дерево и в open.
|
||
for d, v in zip(attrs[attr]["dfs"], attrs[attr]["values"]):
|
||
m = {"name": attr + "=" + v, "edges": [], "df": d.drop(columns=attr)}
|
||
n["edges"].append(m)
|
||
open.append(m)
|
||
pass
|
||
|
||
# Выводим дата сет
|
||
print(df0, "\n-------------")
|
||
|
||
|
||
# Метод преобразования дерева в символы, аргуметы - tree:структура данных древа,
|
||
# indent:присоединяймый к дочернему узлу indent,
|
||
# Возвращаемое значение - символьное представление древа.
|
||
# Этот метод вызывается рекурсивно для преобразования всех данных в дереве в символы.
|
||
def tstr(tree, indent=""):
|
||
# Создаем символьное представление этого узла.
|
||
# Если этот узел является листовым узлом (количество элементов в массиве ребер равно 0),
|
||
# частотное распределение последнего столбца данных df, связанных с деревом, преобразуется в символы.
|
||
s = indent + tree["name"] + str(cstr(tree["df"].iloc[:, -1]) if len(tree["edges"]) == 0 else "") + "\n"
|
||
# Зацикливаем все ветви этого узла.
|
||
for e in tree["edges"]:
|
||
# Добавляем символьное представление дочернего узла к символьному представлению родительского узла.
|
||
# Добавляем еще больше символов к indent этого узла.
|
||
s += tstr(e, indent + " ")
|
||
pass
|
||
return s
|
||
|
||
|
||
# Выводим древо в его символьном представлении.
|
||
print(tstr(tree))
|