2023-11-22 22:49:45 +04:00

119 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))