CSV (comma-separated values)
Таблицы в формате CSV – это просто!
Для начала вспоминаем, как просто прочитать файл.
В питоне есть и другой способ: когда файл используется в том контексте, где питон ожидал бы список, файл воспринимается как список его строк:
Теперь каждую строку нам нжуно разбить по разделителю. Английский эксель использует запятую, русский – точку с запятой.
Например, попробуем взять таблицу, содержащую только числа, и просуммировать числа в строке.
Мы уже умеем всё, что для этого нужно: разбивать строку по разделителю.
Попробуем:
Упс, не работает. На первой же строке у него в sum лежит 0, а в element лежит "3", то есть sum += element расшивровывается как sum = sum + element, то есть sum = 0 + "3"
Ошибочка!
Добавляем преобразование строки в число:
Оффтопик: with
И тут обнаруживаем, что в нашем файле с примером были буквы, и всё снова сломалось.
Заметим заодно, что питон не закрыл за собой файл. Например, поэтому windows не разрешает нам подсунуть вместо него другой.
И даже если мы ишем file.close(), ничего не меняется. Дело в том, что программа ломается в строчке sum += int(element), и до строки file.close() просто не доходит.
Ровно для таких случаев в питоне есть конструкция with: она гарантирует, что файл будет закрыт в тот момент, когда питон выйдет за её пределы – то ли потому, что исполнение дошло до следующей за ней строкой, то ли потому, что случилась ошибка.
Пишется это так:
Хорошая привычка просто работать с файлами всегда чере неё.
Возвращаемся к CSV
Наша задача была просуммировать числа в каждой строке.
Мы разобрались, что намнужно что-то делать с ячейками, в которых не числа. Давайте это напишем:
Но что-то получается маловато. Если приглядеться, обнаруживаем, что питон не прибавляет значение последней ячейки в строке.
Если ещё пристальнее приглядеться (например, воткнуть куда-нибудь print()), мы обнаружим, что в последней ячейке лежит значение '3\n' – этот перенос строки обманывает проверку isdigit().
То есть нам нужно отрезать из конца строки лишние переносы строки:
Словари
Словарь – это как список, только в списке мы можем в качестве индекса элемента использовать только число, а в словаре мы можем использовать в качестве индекса почти что угодно:
Когда мы словарь используем в том месте, где питон ожидает список, то питон воспринимает словарь как список его ключей:
Для словарей не определён порядок элементов (и он может меняться в процессе жизни словаря), поэтому для него бессмысленно понятие слайсов.
Кортежи
Питон запрещает в качестве ключа в словаре использовать изменяемые сущности: списки и словари.
Поэтому в питоне есть специальный тип данных – кортеж – который ведёт себя в точности как списки, но его нельзя менять. Пишется он как просто несколько значений через запятую (а если возникает омонимия, то его нужно взять в круглые скобки):
С кортежами мы можем хранить более сложные отношения. Например:
Пример: собираем данные из нескольких табличек
Для примера попробуем взять несколько табличек: в первой каждая строка описывает качество человека, при этом про одного человека может быть несколько качеств.
В другой табличке есть список людей, про которых мы хотим узнать их качества. Можно считать это миниатюрным запросом к очень простой базе данных, которую мы храним в виде CSV.
Итого нам нужно:
- прочитать файл qualities.csv
- каждую его строку разделить на имя и качества
собрать в словарь все качества для одного имени – в такой формулировке мы не можем это сказать питону, но мы можем это переформулировать по шагам: берём очередного человека, и если мы его знаем, добавить очередных качеств в его описание, а если мы его не знаем, то считаем, что мы до сейчас знали про него ничто.
- пройти по списку людей и для каждого из них распечатать его качества:
1 with open("qualities.csv") as file:
2 qualities = {}
3 for line in file:
4 line = line.rstrip()
5 elems = line.split(',')
6 name = elems.pop(0)
7 if name not in qualities:
8 qualities[name] = []
9 qualities[name].append(elems)
10
11 with open("people.csv") as file:
12 for line in file:
13 line = line.rstrip()
14 print(line, qualities[line])
assert
Маленький оффтопик.
Суть строк
состоит в том, чтобы гарантировать, что после них в qualities есть ключ name, и что qualities[name] есть список (возможно, пустой).
Мы это условие можем записать на питоне: name in qualities and type(qualities[name]) == list
Бывает полезным воткнуть в прогамму проверку, что это всегда так – и если эта проверка сломается, то мы будем хорошо понимать, где мы ошиблись.
Для этого существует конструкция assert: мы ей даём условие, и если это условие не выполнилось, то она рушится с ошибкой:
1 with open("qualities.csv") as file:
2 qualities = {}
3 for line in file:
4 line = line.rstrip()
5 elems = line.split(',')
6 name = elems.pop(0)
7 if name not in qualities:
8 qualities[name] = []
9 assert name in qualities and type(qualities[name]) == list
10 qualities[name].append(elems)
11
12 with open("people.csv") as file:
13 for line in file:
14 line = line.rstrip()
15 print(line, qualities[line])
Частотный список
Следующая задача: построим частотный список слов.
Для этого нужно начать с того, что открыть файл, прочитать его и токенизировать. Это мы уже умеем:
В нашем файле оказалось много мусора из-за того, что это html, а не txt, пробуем чистить и его:
И обнаруживаем, что кроме очень частотных тэгов есть и другой мусор. Значит нам нужно завести список мусорных тэгов и выкидывать все токены, которые в него попадают:
1 with open("karenina.htm", encoding="cp1251") as file:
2 tokens = file.read().split()
3
4 garbage = ['', 'dd>  ', 'b', 'p',
5 'align="center',
6 ]
7
8 clean_tokens = []
9 for token in tokens:
10 token = token.strip("/:'!?-,.\";<>()")
11 if token not in garbage:
12 clean_tokens.append(token)
Теперь стало похоже на чистый корпус.
Теперь считаем частоты.
Самый простой способ: заводим словарь, в котором ключ – слово, значение – его частота. И каждый раз, когда видим слово, добавляем 1 к его частоте. Но ещё нужно сказать, что когда мы видим слово первый раз, его частота равна 1:
1 with open("karenina.htm", encoding="cp1251") as file:
2 tokens = file.read().split()
3
4 garbage = ['', 'dd>  ', 'b', 'p',
5 'align="center',
6 ]
7
8 clean_tokens = []
9 for token in tokens:
10 token = token.strip("/:'!?-,.\";<>()")
11 if token not in garbage:
12 clean_tokens.append(token)
13
14 frequency = {}
15 for token in clean_tokens:
16 if token not in frequency:
17 frequency[token] = 1
18 else:
19 frequency[token] += 1
20
21 print(sorted(frequency)[:100])
Можно условие чуть-чуть сократить: если мы слово не видели, то его частота 0, а потом в любом случае прибавляем к нему 1. (Это тот же приём, что мы использовали для ассоциации качеств с людьми.
1 with open("karenina.htm", encoding="cp1251") as file:
2 tokens = file.read().split()
3
4 garbage = ['', 'dd>  ', 'b', 'p',
5 'align="center',
6 ]
7
8 clean_tokens = []
9 for token in tokens:
10 token = token.strip("/:'!?-,.\";<>()")
11 if token not in garbage:
12 clean_tokens.append(token)
13
14 frequency = {}
15 for token in clean_tokens:
16 if token not in frequency:
17 frequency[token] = 0
18 frequency[token] += 1
19
20 print(sorted(frequency)[:100])
Или можем переписать так: сделаем переменную x, в которой лежит 0, если мы до этого слова не видели, и его прежняя частота, если мы его видели:
1 with open("karenina.htm", encoding="cp1251") as file:
2 tokens = file.read().split()
3
4 garbage = ['', 'dd>  ', 'b', 'p',
5 'align="center',
6 ]
7
8 clean_tokens = []
9 for token in tokens:
10 token = token.strip("/:'!?-,.\";<>()")
11 if token not in garbage:
12 clean_tokens.append(token)
13
14 frequency = {}
15 for token in clean_tokens:
16 if token not in frequency:
17 x = 0
18 else:
19 x = frequency[token]
20 frequency[token] = x + 1
21
22 #print(sorted(frequency))
В питоне есть функция, которая ровно это и делает. Метод dict.get(key, defaultvalue) делает ровно это: если ключ key в словаре есть, то он возвращает значение dict[key], а если его не было, то возвращает defaultvalue.
С ним мы можем переписать подсчёт частот в три строки:
1 with open("karenina.htm", encoding="cp1251") as file:
2 tokens = file.read().split()
3
4 garbage = ['', 'dd>  ', 'b', 'p',
5 'align="center',
6 ]
7
8 clean_tokens = []
9 for token in tokens:
10 token = token.strip("/:'!?-,.\";<>()")
11 if token not in garbage:
12 clean_tokens.append(token.lower())
13
14 frequency = {}
15 for token in clean_tokens:
16 frequency[token] = frequency.get(token, 0) + 1
17
18 print(sorted(frequency, key=frequency.get)[-100:])
19 print(list(reversed(sorted(frequency, key=frequency.get)))[:100])
20 print(sorted(frequency, key=frequency.get, reverse=True)[:100])