Вводная
В рамках расширения своих компетенций периодически провожу анализ данных датасетов. В какой-то момент осознал, что трачу время на столбцы с аналитиками, в которых все в порядке. Данные полные, тип данных единый, интерпретация понятна. Если столбцов несколько десятков, то обзорная проверка атрибутов каждого столбца занимает довольно значительное время.
Посмотрел в сторону библиотеки pandas-profiling.
Мне показалось, что инструмент хорошо подходит для датасета в котором отработаны аномалии, пропуски, выбросы, типы данных. Вызвав df.profile_report() получаешь добротный отчет и остается только рыться во всех вкладках отчета и анализировать интересующие столбцы.
Меня смутил большой объем информации, который выдает profile_report(). Мне не хватило более простой обзорной формы, где легко можно сделать вывод и решить для себя стоит обратить внимание на столбец или нет. Возникла потребность самому настроить выдачу отчета под себя.
Разработчиком я не являюсь, поэтому попытался накидать простой код своими силами, который бы позволил немного ускорить процесс проверки. Возможно он поможет и вам. Был бы признателен, если в комментариях, напишите ваше предложение, что можно улучшить, добавить, скорректировать.
Требования и план разработки
При построении нужно предусмотреть добавление функций для анализа данных, если возникнет потребность расширить пул анализируемых параметров. Возможность вызывать, как отдельную функцию с конкретным анализом, так и все сразу. Функции должны перебирать каждый столбец в DateFrame.
Подумав над решением пришел к выводу, что понадобиться класс, в котором инициализируются переменные, которые будут доступны всем функциям. Графически вижу это так:
![Графическое изображение плана разработки класса Графическое изображение плана разработки класса](https://habrastorage.org/getpro/habr/upload_files/274/b46/c10/274b46c106472ebe6f696b34eb476ed9.jpg)
Подумал, как выстроить алгоритм для анализа столбцов. На вход функция получает таблицу с данными, анализирует через цикл каждый столбец. По итогу должна родиться строка с результатом. Один столбец = одной строке с результатом.
Функция должна иметь возможность быть вызвана независимо от других, следовательно, чтобы удовлетворить это требование результатом работы (return) должен быть DateFrame
![Графическое изображение алгоритма работы функции для анализа Графическое изображение алгоритма работы функции для анализа](https://habrastorage.org/getpro/habr/upload_files/855/99d/1d9/85599d1d9bc39e927a9e65f6067b7b7c.jpg)
Функция вывода всех результатов анализа, решил построить следующим образом
![Графическое изображение алгоритма работы функции для сбора всего анализа Графическое изображение алгоритма работы функции для сбора всего анализа](https://habrastorage.org/getpro/habr/upload_files/d89/f1a/bc9/d89f1abc907c015364dff1eee49b0dbd.jpg)
Разработка
Инициализируем класс
На вход подаем Dataframe
import pandas as pd
class DataAnalysisColumns():
def __init__(self, df: pd.DataFrame):
self.df = df
self.columns = df.columns
self.TotalRows = df.shape[0]
self.BoxResult = []
self.СolumnsDict = {
'AnalysisParams':'',
'NameColumns' : '',
'Value': ''}
Делаем доступными для функций:
Наименование атрибута |
Описание |
self.df |
Dataframe |
self.columns |
Список с названием столбцов |
self.TotalRows |
Общие количество строк. Понадобиться для части функций. |
self.BoxResult |
Пустой список для сохранения результата анализа столбцов. |
self.СolumnsDict |
Словарь с названиями столбцов итогового отчета. |
Общая функция
CreateDf принимает список с результатом анализа и создает DataFrame. Очищает список, в котором хранились результаты с целью использования в следующих функциях.
def CreateDf(self,BoxResult:list):
OutputDf = pd.DataFrame(data=BoxResult, columns=list(self.СolumnsDict.keys()))
self.BoxResult.clear()
return OutputDf
Функции анализа
Напишем несколько простых функций с анализом. Проанализируем имена столбцов на предмет наличия пробелов. Ранее при использовании query в pandas пробелы в названиях столбцов попортили нервы.
Второй анализ будет связан с наличием пустых строк в столбце. Выведем абсолютное и относительное число.
Каждая функция завершается созданием отдельного dataframe
def AnalysisOfColumnNames(self):
for NameColumn in self.columns:
if " " in NameColumn:
ListAttributes = ['Сolumn name', NameColumn, NameColumn]
self.BoxResult.append(ListAttributes)
return self.CreateDf(self.BoxResult)
def AnalysisOfNull(self):
for NameColumn in self.columns:
CountNull = self.df[NameColumn].isnull().sum()
if CountNull > 0:
ListAttributes = ['Null Count',
NameColumn ,
f'{CountNull} of {self.TotalRows} or {"{0:.0%}".format(CountNull/self.TotalRows)}']
self.BoxResult.append(ListAttributes)
return self.CreateDf(self.BoxResult)
Третья функция немного сложнее. Анализируем состав типа данных внутри столбца. Результаты выводим так же в абсолютных и относительных значениях.
В строках 18-20 пытаюсь выдернуть из <class 'str'>, тип данных c кавычки 'str'. Переменные FirsttNumOfSymbol, LatsNumOfSymbol. Наверное, его можно заменить регулярным выражением. Пытался сделать не получилось. Был бы рад, если в комментариях подскажите это сократит код.
def AnalysisOfType(self):
# анализируем каждый столбец
for NameColumn in self.columns:
TypeColumn = str(self.df[NameColumn].dtype)
# для типа 'object' пробегаемся по всему столбцу
# формируем сводную по типу данных и считаем кол-ву строк
if TypeColumn == 'object':
self.df['TypeData'] = self.df[NameColumn].apply(lambda x: str(type(x)))
PivotTable = self.df.groupby('TypeData').agg({'TypeData': ['count']}).reset_index()
PivotTable.columns = ['TypeData', 'count']
ColumnType = PivotTable['TypeData']
BoxResult = []
# собираем все в один результат(строку)
for i in range(len(PivotTable)):
FirsttNumOfSymbol = ColumnType[i].find("'")
LatsNumOfSymbol = ColumnType[i].rfind("'") + 1
NameTypeRow = ColumnType[i][FirsttNumOfSymbol:LatsNumOfSymbol]
Percent = "{0:.0%}".format(PivotTable['count'][i]/self.TotalRows)
StringForAppend = f"{NameTypeRow}:{PivotTable['count'][i]}({Percent})"
BoxResult.append(StringForAppend)
ListAttributes = ['Type',
NameColumn,
",".join(BoxResult)]
self.BoxResult.append(ListAttributes)
return self.CreateDf(self.BoxResult)
Функции для анализа выбросов и анализ уникальности я оставлю в коде на GitHub.
Сбор всего анализа
Наши функции выдают отдельные DataFrame, соберем их в отдельный список. При помощи concat объединим в один.
В качестве демонстрации работы с итоговой таблицей, изменил порядок столбцов и добавил сортировку.
def AnalysisOfDf(self):
ListOfAnalysis = [self.AnalysisOfColumnNames(),
self.AnalysisOfNull(),
self.AnalysisOfType(),
self.AnalysisOfOutlier(),
self.AnalysisOfUniqueText(),
]
OutputDf = pd.concat(ListOfAnalysis).reset_index(drop=True)
OutputDf = OutputDf[['NameColumns', 'AnalysisParams', 'Value']]
return OutputDf.sort_values(by=['NameColumns']).reset_index(drop=True)
Результат
Тестируем на реальных данных. Вызовем весь анализ. Размер таблицы (7043, 24)
![Вывод функции AnalysisOfDf Вывод функции AnalysisOfDf](https://habrastorage.org/getpro/habr/upload_files/133/82f/d4b/13382fd4b81c263fbfcc28b024d5049b.png)
Посмотрим, как работает отдельно функция по анализу типов данных
![Вывод функции AnalysisOfType Вывод функции AnalysisOfType](https://habrastorage.org/getpro/habr/upload_files/c41/b8e/4e3/c41b8e4e34e714372515c8b22d59af5e.png)
Заключение
Данное решение является для меня самым первым этапом анализа. Потенциальные проблемы или их отсутствие видны в таблицы. После этого можно начинать следующие шаги и сфокусироваться на требующих детальной разборки столбцах.
Повторюсь, если у вас есть предложения, что можно улучшить, добавить, скорректировать прошу в комментарии.
Код и данные на GitHub
Комментарии (5)
folal
16.04.2023 07:07Вот у вас фрейм Результат, первая строка вывода, два уникальных значения - yes,no. И что?
А если категорий пятьдесят, вы их все будете выводить?
Вторая строка, str 100%, и что?
Иначе говоря, не видно смысла анализа.
Намного сильнее, думаю, когда анализ указывает разработчику на варианты дальнейших действий. Если ваш результат анализа отметит некий признак как мусор - вероятно, надо удалять. Если пропущенных значений запредельно много, или признак статический, или сработал increasing/decreasing - мусор, видимо. Если пропусков допустимо - то отметить для замены. Если признак временнОй или строка хитрого формата - отметить для дальнейшего парсинга. Богатый разветвленный анализ, исполненный в таком ключе, я бы с удовольствием присмотрел к своей работе.DmitriyB_33 Автор
16.04.2023 07:07+1Добрый день. Мне кажется в вопросе анализа и интерпретации результатов у каждого свои внутренние потребности, что хочется увидеть. Конкретно у меня была потребность обзорно посмотреть на столбцы все разом и решить для себя стоит обратить внимание на конкретный столбец или нет.
Вопрос: первая строка вывода, два уникальных значения - yes,no. И что? А если категорий пятьдесят, вы их все будете выводить?
Ответ: в данном случае это мне говорит, о содержание значений в столбце и не каких дополнительных действий для нормализации данных в общем, то и не требуется. Там могло быть например ['Yes' ,''yes', 'NO', 'Nope'] и тогда возможно я бы причесал значения. В алгоритме больше 5 уникальных выводиться не будет. Например для столбца MothlyCharges 1585 уникальных без их перечисления.
Вопрос: Вторая строка, str 100%, и что?
Ответ: это мне говорит, что в столбце нет сборной солянки и отдельно мне проверять не нужно, что не так. А например в столбце TotalChange состав следующий 'float':6708(95%),'int':324(5%),'str':11(0%). В этом столбце нужно разбираться. Добавлю, что алгоритм анализирует только тип object, остальные не смотрит. Так в object именно может быть сборная солянка.
Комментарий: Намного сильнее, думаю, когда анализ указывает разработчику на варианты дальнейших действий.
Ответ: Абсолютно согласен и поддерживаю. Поэтому смотрел изначально в сторону pandas-profiling. Анализ хороший, но отчет мне показался сильно нагруженным. Мой кодик пробегает по верхам и просто говорит "обрати внимание" или "не обращай и так все понятно". Возможно действительно стоит добавить 4-ый столбец с выводом по строке, чтобы вопросов как это понимать не возникало. Так же старался разработать код, чтобы туда можно было легко дописать новые функции и в конечном итоге получился индивидуальный отчет у пользователя. В целом с вами согласен, если дальше развивать код, то вполне может получится симпатично.
folal
16.04.2023 07:07Ответ: это мне говорит, что в столбце нет сборной солянки...
Я вот об этом. - если нет, то и выводить не надо, в ваших результатах 90% вывода не требует вашего внимания.
igor_suhorukov
Было бы интересно узнать почему предпочитаете Pandas а не Polars. Во многих случаях можно было бы и не профилировать
DmitriyB_33 Автор
Спасибо за наводку. Ознакомлюсь с Polars.