четверг, 16 марта 2017 г.

Bluetooth notes

Реализация стек протоколов Bluetooth в Linux называется BlueZ. В него включены утилиты:
  • hciconfig - аналог ifconfig;
  • hcitool - сбор информации;
  • hcidump - перехват данных.
HCI - Host controller interface.

Просмотреть доступные Bluetooth адаптеры:
hciconfig
hci0:    Type: BR/EDR  Bus: USB
    BD Address: 11:22:33:44:55:66  ACL MTU: 1022:8  SCO MTU: 121:3
    UP RUNNING PSCAN
    RX bytes:637 acl:0 sco:0 events:44 errors:0
    TX bytes:4038 acl:0 sco:0 commands:44 errors:0

Включать\выключать Bluetooth адаптер:
hciconfig hci0 down
hciconfig hci0 up

Разведка

Обнаружение устройств.
Далее предполагаем, что устройства доступны для обнаружения.
hcitool scan
Утилита выводит только адрес и имя устройства

 

Чтобы собрать больше информации используем команду inq
hcitool inq
Здесь указывается смещение часов и тип устройства (телефон, bluetooth гарнитура, и т.д.)


 

Детальная информация об устройстве.
hcitool info <addr>

Работа с устройствами

Отправка файлов
bluetooth-sendto --device=11:22:33:44:55:66 t2.png

bluetoothctl - интерактивная командная оболочка для взаимодействия с bluetooth устройствами. Команда help выдаст список всех доступных команд [so].
Сканирование тут выполняется автоматически, его только нужно предварительно включить
scan on
devices просмотреть доступные устройства. Причём устройства сохраняются в этом списке после обнаружения: устройство может быть уже не видимым, но по прежнему доступно для коммуникации.
agent on
pair 11:22:33:44:55:66
После чего необходимо подтвердить цифровой ключ на обоих устройствах.

Доступ к файловой системе устройства. На устройстве должна быть установлена служба OBEX FTP.

Монтировать устройство:
obexfs -b 70:F9:27:76:8D:61 -- mount_here
Теперь можно просматривать и копировать файлы.

Bluetooth и Python

Поиск доступных для обнаружения устройств
from bluetooth import *
devList = discover_devices()
for device in devList:
    name = str(lookup_name(device))
    print("[+] Found Bluetooth Device " + str(name))
    print("[+] MAC address: "+str(device))

Ссылки

Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security… by TJ O'Connor

Как взломать Bluetooth, Часть 1: термины, технологии и безопасность

понедельник, 15 августа 2016 г.

Как собрать wifi probe requests

Рассылка телефоном probe request

Телефон с включённым WiFi может периодически отправляет в эфир запросы, т.н. probe requests, содержащие MAC самого устройства и имя известной WiFi сети, с которой он соединялся ранее в надежде, что эта сеть ему ответит. Этот механизм, активный поиск, является альтернативным пассивному, когда устройство желая соеденится с сетью слушает эфир ожидая маячковые сообщения (beacon frames) от точки доступа [Passive WiFi Tracking].

Если устройство подключено к сети, то ему не нужно оповещать другие сети о себе. Однако, отключив устройство от точки доступа (например послав фрейм деаутентификации с помощью aireplay-ng: aireplay-ng -0 5 -a <bssid> -c <client_mac> <interface>) можно рассчитывать на то, что устройство будет зондировать эфир рассылая запросы.

Их личного опыта могу сказать, что телефон может отправлять probe request при включении или при выходе из режима "в самолёте" даже если wifi выключен в настройках.

Посмотреть пристальнее на эти пакеты можно в Wireshark используя фильтр wlan.fc.type_subtype == 4

Wireshark packet screenshot
На рисунке прежде всего интересны поля SSID - имя сети и MAC адрес устройства, которое и отправило этот пакет. Стоит отметить, что BSSID точки доступа не передаётся.
Таким образом можно привязать конкретное устройство к wifi сети (с поправкой на то, что сетей с данным именем может быть несколько) и далее попытаться определить географическое положение этой сети. Последнее возможно при помощи геоинформационных сервисов в Интернете, например WiGLE (поиск по сетям доступен после регистрации). Если в Сети данных нет, то можно обойтись собственноручно собранной информацией о местоположении WiFi сетей, например с помощью мобильных приложений (Google Play: Wigle Wifi Wardriving).

Приём probe request

Для того, чтобы принимать probe requests в режиме реального времени, включим на WiFi карте режим монитора:
airmon-ng start wlan0
а затем запустим версию wireshark с текстовым интерфейсом:
tshark -i wlan0 subtype probereq
При желании вывод программы можно настроить:
tshark -i wlan0mon  -Y 'wlan.fc.type_subtype == 4' -T fields -e frame.time -e wlan.sa_resolved -e wlan_mgt.ssid
-i wlan0mon интерфейс - wifi карта в режиме монитора;
-Y 'wlan.fc.type_subtype == 4' фильтр для печати пакетов на экран;
-T fields - определим, какие конкретно данные каждого пакета выводить на экран, приведя отдельные поля после ключа -e:
frame.time - время
wlan.sa_resolved - MAC адрес источника пакета, где половина адреса заменена на название его производителя (vendor). Если необходимости в последнем нет, то печатаем просто MAC: wlan.sa.

wlan_mgt.ssid - самое интересное, SSID сети.

Созерцание вывода программы можно соединить с одновременной записью в файл:
tshark -i wlan0mon  -Y 'wlan.fc.type_subtype == 4' -T fields -e frame.time -e wlan.sa_resolved -e wlan.sa -e wlan_mgt.ssid | tee -a probes.dump

tee - для одновременного вывода и записи в файл. tshark при печати на экран использует буфер, а ключ -l заставляет утилиту очищать буфер при выводе каждого пакета, таким образом печать будет происходить одновременно с обработкой пакета.
Отфильтруем probe request с пустым SSID (широковещательные?) добавив к фильтру выражение wlan_mgt.ssid != 0

tshark -i wlan0mon  -Y 'wlan.fc.type_subtype == 4' -T fields -e frame.time -e wlan.sa_resolved -e wlan.sa -e wlan_mgt.ssid | tee -a probes.dump
Наконец можно направив вывод через grep показывать только определённые устройства или же избавить себя от их упоминания (ключ -v):
tshark ... | grep -v -e "11:11:11:11:11:11" -e "11:11:11:11:11:22"
Сделать задачу мониторинга немного проще призван скрипт shee.sh
./shee.sh -i wlan0mon 

Кроме того, утилита может работать в нескольких режимах. Например ждать появления определённого MAC адреса в эфире и при обнаружении подавать звуковой сигнал:
sudo shee.sh -i wlan0mon -M 1 -m 11:22:33:44:55:66 -s
-M 1 - следить за конкретным MAC адресом
-m тот самый mac адрес
-s - при обнаружении подавать звуковой сигнал

***
Наблюдать устройства посылающие probe requests можно и в airodump-ng.  Утилита показывает активные устройства (мак, время появления, время последнего пакета, ESSID и т.д.). в реальном времени. Если включена запись в файл, то данные об эфире также будут сохранены в CSV файл.

Scapy

Создавать, отправлять и принимать этот тип wifi кадров, можно с помощью мощного пакета Scapy. Он включает утилиту командной строки, в которой можно выполнять операции в интерактивном режиме, и пакет для Python.

Ссылки

WiGLE - ГИС

понедельник, 1 августа 2016 г.

Raspberry Pi Notes


Установка ОС

Будем использовать Raspbian. Подготовка SD карты будут произведена в ОС Ubuntu.


1. скачать образ raspberrypi.org/downloads/raspbian/

2. распаковать
unzip 2018-03-13-raspbian-stretch.zip

3. Записать образ на SD карту.
Определить как называется SD карта в системе
lsblk

Скорее всего это что-то вида mmcblk0p1

от монтировать карту
umount /dev/mmcblk0p1

Записать образ на карту

sudo dd bs=4M if=2018-03-13-raspbian-stretch.img of=/dev/mmcblk0 conv=sync status=progress

Имя устройства должно быть  mmcblk0, не mmcblk0p1

Нижеприведённое справедливо для Raspberry Pi B+ с установленной Raspbian Jessie.

Настройка удалённого доступа

Включение ssh 

Поиск Raspberry Pi в сети

Светодиоды на плате [stackexchange]

Красный светодиод - индикатор питания. Начинает мигать если на вход платы подаётся напряжение менее 4.63 вольта, иначе горит непрерывно.
Проблемы с напряжением могу возникать и при некорректной работе (или некорректном подключении) присоединенной к GPIO переферии.
Зелёный светодиод - индикатор доступа к microSD.

Питание [raspberrypi faq]

Для корректной работы RPi B+ компьютера необходимо 1.8 А, 5 В, но может понадобится и больший (до 2.5 А) если задействованы все 4 USB порта.
Внешние жёсткие диски, без дополнительного питания часто отказываются работать. Проблему можно решить покупкой USB хаба, с собственным источником энергии. 

Первоначальная настройка

После подготовки SD карты (с Raspbian) и загрузки имеет смысл провести начальную конфигурацию запустив программу raspi-config от имени суперпользователя:
sudo raspi-config
Далее в режиме мастера настроить:
- использование всего объёма карты памяти,
- изменить пароль пользователя pi (пароль по умолчанию: raspberry),
- установить язык, раскладку клавиатуры и время,
- и многие другие параметры.

Настроить SSH сервер [Памятка пользователям ssh]

Температура компонентов [cyberciti.biz]

Температура CPU
cat /sys/class/thermal/thermal_zone0/temp
47078
Полученную цифру нужно поделить на 1000 чтобы получить температуру в градусах Цельсия.

Температура GPU
vcgencmd measure_temp
temp=47.1'C
Температура измеряется одним и тем же датчиком ибо CPU и GPU находятся на одном чипе.

GPIO [raspberrypi.org]

GPIO (general-purpose input/output) - интерфейс ввода/вывода общего назначения. При работе с GPIO удобно пользоваться интерактивной картой.

row of GPIO pins along the top edge of the board
numbering of the GPIO pins is rather weird
Самый простой и быстрый способ работы с GPIO на RPi предоставляет утилита raspi-gpio.
Он позволяет установить состояние пина ввод (ip) или вывод (ip). Во втором случае можно управлять напряжением: dl - low, dh - high.
raspi-gpio set 2 op dh  # пин 2 режим вывода, сигнал high

Работа с термодатчиком DS18B20 подключённым к шине 1-wire

Популярная однопроводная шина 1-wire используется для работы со множеством различных сенсоров и устройств. Удобство работы заключается в том, что каждое устройство работающие с этой шиной имеет уникальный идентификатор. Поэтому однотипные устройства можно соединить к шине параллельно и обращаться к ним по идентификатору.
В Raspberry Pi для работы с шиной 1-wire задействован пин GPIO4.

Диапазон измерений датчика DS18B20: от –55°C до +125°C
точность: 0.5°C в диапазоне от –10°C до +85°C. [pdf]

Установленный Raspbian упрощает считывание данных с датчика: можно читать данные из файловой системы.
Для начала работы с шиной 1-wire необходимо провести некоторые приготовления. А именно, отредактировать файл /boot/config.txt добавив в него строку dtoverlay=w1-gpio. 

Перезагрузив Raspberry Pi попробуем обнаружить датчик по адресу /sys/bus/w1/devices/:
ls -l /sys/bus/w1/devices/
Если датчик функционирует, то в этом каталоге он должен появится как подкаталог с именем вида 28-00000ххххххх. В этом подкаталоге должен находится файл w1_slave, в который и производится запись данных. 

Просмотрим этот файл:
cat /sys/bus/w1/devices/28-000001234567/w1_slave
98 01 4b 46 7f ff 08 10 22 : crc=22 YES
98 01 4b 46 7f ff 08 10 22 t=25500
В последней строке приведена температура. Для перевода её в градусы Цельсия нужно поделить на 1000.

... 

Ссылки

воскресенье, 3 января 2016 г.

О работе с данными и их визуализации в python


Для анализа данных будем использовать следующие пакеты:
  • pandas - загрузка, хранение (в DataFrame), манипуляции;
  • seaborn, statsmodels - визуализация (использует matplotlib);
  • scipy.stats - некоторые статистические функции, в том числе проверка гипотез.
Прекрасный вариантом будет строить интерактивные диаграммы с помощью plotly или включать интерактивные matplot графики в Jupyter Notebook применяя mpld3, но не сегодня :)

Рабой средой будет Jupyter Notebook - удобная оболочка для Питона работающая в браузере. Она может быть практичнее IDE или классической интерактивной оболочки потому, что показывает графики в том же окне, на манер многих математических пакетов.

import pandas as pd

Загрузка данных и просмотр

Загрузим данные из CSV файла, где одно значение от другого отделяет запятая. В этом примере у данных отсутствует заголовок - первая строка файла с названиями полей. Поэтому укажем параметр header=None дабы данные в первой строки не принять за названия полей.
pdata = pd.read_csv('filename.csv', delimiter=',', header=None)
В самом начале посмотрим, что же загрузили:
pdata.info()
0     1307 non-null object
1     0 non-null float64
2     0 non-null float64
3     1306 non-null object
...
17    1500 non-null int64
Итак, загружено 1500 записей (строк файла), 18 полей. В первом поле 1307 записей имеют значения, во втором не одного, и так далее. Определены типы данных в некоторых полях.
Посмотрим более детальную информацию по каждому из полей:
pdata.describe()
1   2   4   7            8   11    13            15               16  \
count   0   0   0   0  1500.000000   0  1500   1497.000000      1497.00
mean  NaN NaN NaN NaN     1.852667 NaN     2  52300.977288   2967182.03
std   NaN NaN NaN NaN     0.837911 NaN     0  19574.930119   1710899.18
min   NaN NaN NaN NaN     1.000000 NaN     2     33.000000      2500.00
25%   NaN NaN NaN NaN     1.000000 NaN     2  46400.000000   1850000.00
50%   NaN NaN NaN NaN     2.000000 NaN     2  55000.000000   2950000.00
75%   NaN NaN NaN NaN     2.000000 NaN     2  62500.000000   3480000.00
max   NaN NaN NaN NaN     4.000000 NaN     2  93650.000000  10500000.00

                17 
count  1500.000000 
mean     10.500000 
std       5.768204 
min       1.000000 
25%       5.750000 
50%      10.500000 
75%      15.250000 
max      20.000000
Первой строкой приведены номера полей. Для некоторых из них вычислены среднее (mean), стандартное отклонение (std), максимальное и минимальные значения, перцентили.
Чтобы табличка выглядела красивее установим количество выводимых цифр для вещественных чисел:
pd.set_option('precision',3)
Посмотреть часть данных:
    # печатаем данные полностью
    pdata
    # ... или только голову (по умолчанию n = 5 строк)
    pdata.head()
    # ... или хвост (по умолчанию n = 5 строк)
    pdata.tail() 
 
Доступ к отдельным полям осуществляется по их названиям, которые содержатся в списке columns:
pdata.columns
Если там ничего нет, то добавим. Предпологаем, что у нас только два поля, ибо число заголовков должно соответствовать числу полей:
pdata.columns = ['price', 'price_m2']
Затем, обратимся к полю price чтобы просмотреть значения:
pdata['price']
или так
pdata.price
Если известен только номер нужного поля, например 10:
pdata1[pdata.columns[10]]
Одновременно можно выбрать несколько полей, например для создания нового объекта DataFrame
df2 = df[df.columns[ [-12,-8,-6,-4,-3,-2] ]] 
А может быть нас интересует конкретный элемент, например в первой записи 10-го поля:
pdata1.iloc[1,10]

DataFrame поддерживает и сложные запросы, например можно указать несколько условий для разных столбцов. Кроме того можно применять функции к отдельным столбцам. Подробнее: https://habr.com/company/ods/blog/322626/

Алсо

В некоторых случаях удобно анализировать данные напечатав их в красивой табличке воспользовавшись пакетом prettytable.

Создание объекта DataFrame из списков

Вместо получения данных из файла, объединим два списка в объекте DataFrame.
price =  [4.65, 2.3, 5.8, 1.35, 4.8, 7.5, 3.3, 2.95, 2.95, 1.8, 1.6, 3.399, 5.9, 3.0, 1.6, 3.3, 1.85,
2.033, 2.23, 1.854, 3.48, 3.5, 2.75, 2.35, 2.95, 3.2, 2.75, 1.55, 2.1, 2.8, 2.8, 1.37, 2.1, 6.2,
3.65, 3.1, 3.35, 3.3, 3.85, 2.2, 2.85, 4.5, 3.3, 1.18, 3.79, 3.25, 3.18, 1.59, 1.3, 2.45, 2.2, 
2.563, 2.1, 1.25, 3.2, 2.1, 3.69, 2.86, 2.37, 3.5, 2.06, 4.3, 4.55, 5.1, 1.9, 2.1, 2.65, 10.5,
2.24, 1.77, 1.35, 2.2]
price_m2 = [58.86, 38.333, 71.604, 40.909, 58.536, 62.5, 55.0, 56.73, 49.166, 64.285, 31.372,
48.557, 93.65, 75.0, 53.333, 68.75, 57.812, 49.585, 53.095, 46.35,  46.4, 67.307, 45.081, 55.952,
60.204, 53.333, 78.571, 43.055, 37.5, 66.666, 41.176, 29.782, 51.219, 87.323, 52.142, 50.0, 67.0,
55.0, 71.296, 42.307, 41.304, 57.692, 47.142, 28.78, 57.424, 50.781, 46.086, 49.687, 40.625, 61.25,
43.137, 51.26, 47.727, 50.0, 61.538, 47.727, 47.307, 42.058, 47.4, 53.846, 46.818, 57.333, 56.875,
43.589, 5.882, 61.764, 41.406, 55.851, 48.695, 42.142, 28.723, 53.658]
# создадим DataFrame из 2-х списков сразу указав соответствующие спискам имена полей. 
# Пусть в этом примере имена списков и имена полей совпадают:  
df = pd.DataFrame({'price': price, 'price_m2': price_m2})

Обработка данных

Далее, стоит привести данные к пригодному для обработки виду.
Удалить одинаковые записи, если в этом есть необходимость:
pdata1 = pdata.drop_duplicates()
Положив именованный параметр inplace функции drop_duplicates равным True избавляемся от создания нового объекта, как это происходило выше. Теперь можно записать так
pdata.drop_duplicates(inplace=True)
pdata теперь не содержит дубликатов.
Многие функции имеют подобный параметр, что даёт возможность избавиться от лишнего копирования.

Сохраним данные в файл
pdata.to_csv('data.csv') 
Алсо
Конвертировать текстовое представление даты в дату (объект datetime)
Предположим, что поле time состоит из записей вида 2016.08.06 18:15.
Data.time = pd.to_datetime(Data.time)
Если необходимо, вторым параметром метода to_datetime устанавливается формат даты в исходной строке. Для вышеприведйнной строки формат должен быть описан следующим образом: %Y.%m.%d %H:%M

Графическое представление данных

Чтобы нарисовать красивые графики и диаграммы используем пакет seaborn. Это не единственный способ создавать различные диаграммы и графики (matplotlib), но, наверное, самый красивый.
import seaborn
# для показа сгенерированных картинок используем matplotlib
import matplotlib.pyplot as plt
Далее будем использовать plt без отдельного импорта пакета и иногда будем опускать код, добавляющий необходимые пояснения к представленным на рисунках данным:
# установим заголовок для графика
plt.title("Диаграмма рассеивания: цена квартиры - цена $м^2$"); 
# подпишем оси координат обозначив единицы измерения
plt.xlabel("Цена $м^2$, тыс.руб.")
plt.ylabel("Цена квартиры, млн.руб.")
Если в качестве рабочей среды используется ipython notebook (теперь Jupyter Notebook), то нужно включить интеграцию с matplotlib (графики в том же окне) [so]:
%matplotlib notebook
При необходимости можно установить размер и разрешение графиков:
figure(figsize = (80,40), dpi=100)  # размеры в дюймах
# установить размер шрифтов на графиках
matplotlib.rcParams.update({'font.size': 16})
***
Самый быстрый способ что-нибудь нарисовать - использовать метод plot
df.plot(x='price', y='price_m2', kind='scatter')  # построим диаграмму рассеивания
 параметр kind может принимать значения: 'line', 'bar', 'barh', 'kde', 'density', 'scatter'.
Отображает данные на плоскости или в пространстве. Значения случайных величин откладываются вдоль осей координат. В примере каждая запись из DataFrame представлена точкой с координатами price и price_m2.
seaborn.regplot(x='price_m2', y='price', data=df); plt.show()
В качестве первых двух параметров передаются названия полей (df.columns) DataFrame. Линия тренда отключается параметром fit_reg=False.
Ещё один вид представления диаграммы рассеивания
# нарисуем ещё и линию тренда kind='reg'
seaborn.jointplot(x='price_m2', y='price', data=df, kind='reg');
# подпишем оси координат
plt.xlabel("Цена $м^2$, тыс.руб.");
plt.ylabel("Цена квартиры, млн.руб");
plt.show();
 

Здесь в дополнение к непосредственно данным приведён линейный коэффициент корреляции (Пирсона) - pearsonr и  p-уровень значимости.
Ибо p-value меньше уровня значимости 1% (0.00013 < 0.01) гипотеза о статистической взаимосвязи между величинами может считается верной.

Отмечу, что у меня получилось корректно подписать оси координат для графика в центре только после того как был добавлен параметр стиля в функцию jointplot. Фактически на картинке 3 графика и необходимо  указывать какому из графиков предназначаются подпись для осей координат.

Гистограмма и кривая плотности распределения
import seaborn
from matplotlib.pyplot import show  # чтобы смотреть картинки без сохранения
seaborn.distplot(price)  # подготовим гистограмму и кривую распределения
show()  # покажем картинку

При необходимости можно настроить представление добавив параметры в функцию distplot:
fit=norm - нарисовать поверх данных кривую нормального распределения;
label = 'имя' - указать имя для гистограммы и кривой, чтобы не спутать их с другими;
color = 'y' - изменить цвет;
kde = False - не рисовать kernel density estimate (ядерную (?) оценку плотности вероятности), это кривая на графике выше;
hist = False - не рисовать гистограмму.

Boxplot (ящик с усами или диаграмма размаха)
График показывает сразу несколько параметров распределения:
медиану - линия внутри ящика;
стенками ящика (квартили 0.25 и 0.75) ограничены 50% выборки;
Длинна усов рассчитывается по-разомну.
Это может быть просто минимальное и максимальное значения в выборке. Второй вариант - полторы ширины ящика (расстояния между квартилями 0.25 и 0.75). Тогда точками отмечаются выбросы.
seaborn.plt.title("Диаграмма размаха цен за $м^2$")  # Дополним график заголовком
ax=seaborn.boxplot(price_m2) 
ax.set(xlabel='цена за $м^2$, тыс.руб.')  # добавим подпись к оси абсцисс
plt.show()

Несколько диаграмм на одном рисунке doc

# расположим графики по 2 в столбце в 1 столбец
# и пусть графики имеют общую ось Х
f, ax = plt.subplots(2,1, sharex=True)
подпишем каждый график
ax[0].set_title("Гистограмма распределения")
ax[1].set_title('Диаграмма рассеивания')
# а также ось абсцисс. 
# она будет расположена в низу нижнего графика, поэтому добавим подпись только туда
ax[1].set_xlabel('тыс.руб.')
# явно укажем в параметре ax в каком месте какой график изображать
seaborn.boxplot(price, ax=ax[1])
seaborn.distplot(price, ax=ax[0])
# озаглавим всю картинку, а чтобы текст не сливался с остальными подписями сделаем его шрифтом покрупнее
plt.suptitle("Характеристики распределения цен за $м^2$", size=16)
plt.show()

 

 

Пример

from math import *
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm  # для построения qq plot
from scipy.stats import norm, normaltest
import seaborn
 
file = "apartments.csv"
 
# загружаем данные из CSV файла.
# данные представлены списком строк, значения в которых разделены одно от другого зяпятой ","
pdata_ = pd.read_csv(file, delimiter=",")
 
# В файле содержится много лишней информации, удаляем некоторые поля(столбцы) axis=1 указав их номера как в списке
pdata = pdata_.drop(pdata_.columns[[0,1,2,3,4,5,7,9,11,13, -1]], axis=1)
 
# устанавливаем заголовки для полей, в дальнешем их можно использовать как атрибуты
pdata.columns = ['name', 'rooms', 'addr', 'square', 'floor', 'price_m2', 'price']
 
print("Загружено %i записей"%len(pdata))
 
# удалим дубликаты, изменения зразу "запишем" в тот же объект
pdata.drop_duplicates(inplace=True)
 
# После загрузки, числовые значения для некоторых числовых полей имеют тип string
# конвертируем значения этих полей в числа. неконвертируемые значения станут NaN
pdata.price_m2.convert_objects(convert_numeric=True, copy=False)
pdata.price.convert_objects(convert_numeric=True, copy=False)
 
# Удалим все записи (строки) целиком если они содержут Na или NaN хотя бы в одном поле
pdata.dropna(inplace=True)
# или
# заполним недостающие значения для одного поля
pdata.price.fillna(52, inplace=True)
# или для всех полей
pdata.fillna(123, inplace=True)
 
# удалим ещё и записи, где значение поля price_m2 меньше 1000
pdata = pdata[ pdata.price_m2 > 1000 ]
 
# поделим все значения в полях на ...
pdata.price_m2 = pdata.price_m2.map(lambda x: x/1000)
pdata.price = pdata.price.map(lambda x: x/1000000)
 
print("Всего уникальных записей: %i"%len(pdata))
print("Уникальных записей по полям")
# цикл по парам (имя_поля, Series - содежание поля)
# напечатать число уникальных значений для каждого поля
for colname,rows in pdata.iteritems():
    print("{0: <9}: {1}".format(colname, len(pd.unique(rows))))
 
seaborn.plt.title('Диаграмма рассеивания: цена $м^2$ - цена квартиры')
ax = seaborn.regplot(x='price_m2', y='price', data=pdata)
ax.set(xlabel='Цена $м^2$, тыс.руб.', ylabel='Цена, млн.руб.')
plt.show()
 
print("\nРаспределение цен кв. метра квартиры, руб")
print("Среднее: {0}".format(pdata.price_m2.mean()))
print("Медиана: {0}".format(pdata.price_m2.median()))
print("Ср. кв. отклонение: {0}".format(sqrt(pdata.price_m2.var())))
print("Мода(моды):\n{0}".format(pdata.price_m2.mode()))
 
seaborn.plt.title('Плотность распределения цен')
seaborn.distplot(pdata.price, axlabel='Цена, млн.руб.')
plt.show()
 
seaborn.plt.title('Плотность распределения цен за $м^2$')
seaborn.distplot(pdata.price_m2, axlabel='Цена $м^2$, тыс.руб.')
plt.show()
 
sm.qqplot(pdata.price_m2, line='s')
plt.title('Гипотеза о нормальности распределения цен за $м^2$')
t = normaltest(pdata.price_m2)
print("\nПроверка гипотезы о нормальности распределения цен за кв.м. квартиры")
 
if t[1] < 0.05: print("Распределение НЕ является нормальным (p-value = %0.3f > 0.05)"%t[1])
else: print("Цены распределеные нормально (p-value = %0.3f < 0.05)"%t[1])
plt.show()

После того, как данные представленны графически, проверим несколько статистических гипотез.

Проверка гипотезы о равенстве средних.
Предположим, что имеется две выборки. Например массы кошек и котов. Если вычислить средние массы самцов и самок наверняка получатся отличающиеся цифры. Даже если массы самцов и самок у данного вида животных не отличаются. Но прежде чем мы это выясним нужно ответить на вопрос. Какие различия между средними массами выборок считать значимыми, а какие нет, т.е. получившимися в силу случайности измеряемой величины.

Чтобы ответить на этот вопрос во-первых нужно учитывать, обёмы выборок. Что если выборки маленькие, и совершенно случаной в выборку самцов попали только коты-толстяки, а в выборку самок кошки без лишнего веса?)
Конечно чем больше выборка, тем меньше вероятность что туда попадут исключительно коты с нетипичной массой.
Во-вторых нужно учитывать на сколько вообще случайна масса самих кошек и котов. Если мы будем помещать в выборку кошек разной породы, то там будут самые разные массы. Стало быть если разброс масс может быть большой, то можно думать, что и средние тоже могут отличатся сильнее.

Поэтому чтобы сделать обоснованый вывод о равенстве средних нужно учесть характеричтики выборок. Для этого существуют специальные процедуры сравнения. Они называются статистическими критериями (тестами). Некоторые из них.
Одни критерии применимы для маленьких выборок, порядка единиц. Другие можно применять только для больших выборок, а третьи только для выборок распределённых по нормальному закону.

Помимо этого, одни критерии (или их варианты) применимы для зависимых, а другие для независимых выборок.
Зависимые выборки - это те где одному объекту из первой выборки соответствует обхект из второй. Стало быть выборки должны иметь один и тот же объём.
Например близнецов можно рахбить на две выборки. У каждого близнеца будет ровно один брат (или сетра) в другой выборке. Причём не любой брат, а именно его брат.
Другой пример зависимых выборок - рост людей в 20 и в 30 лет. Здесь человеку ставится в соответствие он же сам.

Для независимых выборок нельзя сказать, что именно этому объекту из первой ыборки соответствует именно из второй. Пример с кошками как раз представляет собой две независимые выборки.

Проверим гипотезу о равенстве средних используя t-критерий Стьюдента. (Притворимся что выборки имеют нормальное распределение).
Нулевая гипотеза здесь (как и во многих похожих тестах) следующая: математические ожидания равны. Если точнее, то равны именно математические ожидания генеральныз совокупностей из которых сделаны выборки. Ведь выборочные мат.ожидания мы и без критериев сравнить можем.

from scipy.stats import *

y = [16.8, 18, 20.3, 17.3, 18.9, 18]
x = [18.5, 17.5, 18.5, 19.8, 17.9, 19.4] 
 
ttest_ind(x, y)
 
# Результат
Ttest_indResult(statistic=0.61784218471924157, pvalue=0.55048986906612796)
 
В результате нам интересно p-value. Оно здесь заметно больше всякого разумного уровня значимости (например 0.05) поэтому оснований для отклонения нулевой гипотезы нет: матожидания генеральных совокупностей равны.

Другие статистические критерии для проверки равенства средних:
ttest_rel(x,y) - t-критерий для зависимых выборок.
ttest_ind(x,y) - t-критерий для независимых выборок.
wilcoxon(x,y) - критерий Уилкоксона (зависимые выборки) 
mannwhitneyu(x, y, ...) - U-критерий Манна — Уитни (независимые выборки)
 






Вычисление коэффициента корреляции Пирсона и проверка его значимости.
Коэффициент корреляции Пирсона (лиейный коэффициент корреляции) описывает силу линейной зависимости между двумя случайными величинами. Он принимает значения в отрезке от 1 до -1. Значение к.к. Пирсона близкое к нулю говорит об отсутствии (слабой зависимости) линейной зависимости между случайными величинами. Значение близкое по модулю к еденице, наоборот говорит о сильной линейной зависимости. Стоит отметить, что случайные величины могут быть связаны ещё и нелинейной зависимостью, тогда к.к. Пирсона здесь плохой помошник. Кроме того, вычисляя к.к. по выборке можно совершенно случайно получить то или иное значение к.к., поэтому имеет смысл оперировать ещё и степенью уверенности для этого значения. В математической статистике для этого служит p-value - вероятность получить такое или ещё более выраженное значение случайно. На практике, особенно при небольших объемах выборки нулевого p-value добится не получается, поэтому довольствуются некоторыми малыми вероятностями того, что величниа (в нашем случае к.к) получена не случайно. Обычно, в соответствии с принципом практической невозможности маловероятных событий, принимают допустимую вероятность ошибки в 0.05 или 0.01. Однако стоит обратить внимание на то, что при многократном извлечении выборки и вычислении параметра, можно получить желаемое значение параметра совершенно случайно.
from scipy.stats import perasonr
# функция возвращает к.к. Пирсона и p-value 
# проверяется гипотеза о том, что такое значение к.к. Пирсона получено случайно
r,p = pearsonr(price_m2, price)
>>>(0.48992003383028732, 1.2541938539342937e-05)
В итоге имем значение к.к. r= 0.49
p-vlue = 0.000013 < 0.01, значение гораздо меньше допустимой вероятность ошибки - отвергаем гипотезу о случайности данного значения к.к.Пирсона. Этому значению r можно доверять.

***

Генерирование значений случайной величины


Модуль scipy.stats содержит множество классов, каждый из которых предназначен для работы с определённым распределением.

Несколько примеров:
scipy.stats.uniforn - равномерное распределение
scipy.stats.norm - нормальное распределение
scipy.stats.t - распределение Стьюдента (t-распределение)
scipy.stats.poisson - распределение Пуассона

Эти классы имеют схожие методы для генерирования случайных значений - rvs.

Генерирование одного значения СВ распределённой по стандартному нормальному закону:
scipy.stats.norm.rvs()

Различия в вызове функции rvs для каждого подмодуля - это различия в параметрах распределений.
Например равномерное распределение (uniform) имеет два параметра определяющие минимальное и максимальное значение СВ.
Нормальное распределение имеет тоже два параметра, но это математическое ожидание и стандартное отклонение.

Параметры функции rvs
функция rvs имеет три параметра которые и задают параметры распределения эти параметры loc, shape, scale
какие из этих трёх параметров нужно задавать и какой смысл они имеют напиcано в документации к подмодулю

параметры указываются так:
scipy.stats.norm.rvs(loc = 12.2)
Для параметра shape приводится просто число:
scipy.stats.norm.rvs(5.72)

Пример

help( scipy.stats.norm )
The location (loc) keyword specifies the mean.
The scale (scale) keyword specifies the standard deviation.
loc - это математическое ожидание
sсale - стандартное отклонение

Отдельным параметром size задаётся количество значений СВ которые нужно сгенерировать.

Создание 100 значений нормально распределённой случайной величины с математическим лжиданием 4 и стандартным отклонением 7.

scipy.stats.norm.rvs(loc=4, scale=7, size=1000)


Друге примеры в jupyter блокноте: https://github.com/VetrovSV/ST/blob/master/Python.%20Statistics.ipynb

 

Вычислить значение функции распределения - CDF
CDF (cumulative distribution function) - функция распределения случайной величины. Значение этой функции - вероятность в которой случайная величина X примет значение меньше заданного x:
F(x) = P(X < x)
Графически это площадь под кривой плотности вероятности (PDF), например такой

На рисунке, вероятность того что случайная величина примет значении меньше 1 закрашена серым. Площадь под любой кривой распределения всегда равна еденице.

При проверке статистических гипотез или вычислении доверительных интервалов нужно вычислять значение функции распределения случайно величины. В каждом конкретной задаче случайная величина может быть распределена по какому угодно закону т.е. иметь свою форму кривой плотности вероятности. 

Кроме проверки гипотез, CDF может пригодится и для вычисления вероятности того, что случайная величина примет значение лежащие в отрезке. Такая вероятность вычисляется по формуле:
P(x1 < X < x2) = F(x2) - F(x1)
Формулу легко представить как разность двух площадей.
В пакете scipy функции распределения имеют схожий интерфейс, у каждой можно вызвать cdf, pdf, isf функции. Приведём пакеты некоторых функции распределения из них.
CDF для стандартного (нормированого) нормального закона и других законов

    from sicpy.stats import norm # нормальное распределение
    norm.cdf(3)
    0.9986501019683699

Это же значение можно подсмотреть и в таблице pdf

from scipy.stats import f  # распределение Фишера
from scipy.stats import t  # распределение Стьюдента (t распределение)
from scipy.stats import chi2  # распределение Хи-квадрат
from scipy.stats import poisson  # распределение Пуассона


Если обозначить площадль слева от заданной ординаты как p (закрашени область на рис. выше), то площадь справа q = 1 - p.
Получить ординату точки по значению q  работа функции isf.

Приведём несколько примеров:

p = 0.95
q = 1 - p
norm.isf(q)
>>> 1.6448536269514722
norm.cdf(_)  # проверим результат
0.94999999999999996


# для некоторых распределений необходимо указывать их параметры, здесь - число степеней свободы
t.isf(0.05, df=100)
>>> 1.6602343260657506
 # t.cdf(_, df=100)  проверим результат
>>> 0.94999999999802531


Алсо

R - язык программирования созданным специально для обработки данных. R Studio - среда разработки.

Ссылки