Wednesday, April 13, 2022

Using JSON in MariaDB

 Следую документу https://mariadb.com/resources/blog/using-json-in-mariadb/

Ставить MySQL 8.X на Fedory 35 - не лучшая идея.

Структурированные данные + Полуструктурированные данные

Существует множество вариантов использования, в которых имеет смысл комбинировать структурированные и полуструктурированные данные. Это просто мир разработки программного обеспечения для вас. Тем не менее, я всегда считал, что проще всего использовать новые технологии, сосредоточившись на простом, (надеюсь) подходящем сценарии использования, который вы затем можете использовать, чтобы получить свои собственные творческие соки.


Чтобы помочь разобраться с возможностями JSON, доступными в MariaDB, я буду использовать гипотетическое приложение. Это приложение будет содержать только одну таблицу, называемую местоположениями, в которой будут храниться, да, как вы уже догадались, местоположения. Достаточно просто, не так ли?

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





СОЗДАНИЕ ТАБЛИЦ

Использовать JSON в MariaDB так же просто, как включить возможность хранить данные JSON в таблице. На самом деле SQL, используемый для создания новой таблицы местоположений, должен выглядеть очень знакомо.





MariaDB [verify]> create database datajs ;

Query OK, 1 row affected (0.001 sec)

MariaDB [verify]> use datajs ;

Database changed

MariaDB [datajs]> CREATE TABLE locations (

->     id INT NOT NULL AUTO_INCREMENT,

->     name VARCHAR(100) NOT NULL,  

->     type CHAR(1) NOT NULL,

->     latitude DECIMAL(9,6) NOT NULL,

->     longitude DECIMAL(9,6) NOT NULL,

->     attr JSON, 

->     PRIMARY KEY (id)

-> );

Query OK, 0 rows affected (0.010 sec)

MariaDB [datajs]> desc locations ;

+-----------+--------------+------+-----+---------+----------------+

| Field     | Type         | Null | Key | Default | Extra          |

+-----------+--------------+------+-----+---------+----------------+

| id        | int(11)      | NO   | PRI | NULL    | auto_increment |

| name      | varchar(100) | NO   |     | NULL    |                |

| type      | char(1)      | NO   |     | NULL    |                |

| latitude  | decimal(9,6) | NO   |     | NULL    |                |

| longitude | decimal(9,6) | NO   |     | NULL    |                |

| attr      | longtext     | YES  |     | NULL    |                |

+-----------+--------------+------+-----+---------+----------------+

6 rows in set (0.001 sec)

MariaDB [datajs]> INSERT INTO locations (type, name, latitude, longitude, attr) VALUES 

->     ('R', 'Lou Malnatis', 42.0021628, -87.7255662,

->       '{"details": {"foodType": "Pizza", "menu": 

'>     "https://www.loumalnatis.com/our-menu"}, 

'>     "favorites": [{"description": "Pepperoni deep dish", "price": 18.75}, 

'>          {"description": "The Lou", "price": 24.75}]}');

Query OK, 1 row affected, 2 warnings (0.001 sec)

=================

MariaDB [datajs]> INSERT INTO locations (type, name, latitude, longitude, attr) VALUES 

->     ('A', 'Cloud Gate', 41.8826572, -87.6233039, 

->           '{"category": "Landmark", "lastVisitDate": "11/10/2019"}');

Query OK, 1 row affected, 2 warnings (0.001 sec)

MariaDB [datajs]> SELECT name, latitude, longitude,

->     JSON_VALUE(attr, '$.details.foodType') AS food_type

-> FROM locations

-> WHERE type = 'R';

+--------------+-----------+------------+-----------+

| name         | latitude  | longitude  | food_type |

+--------------+-----------+------------+-----------+

| Lou Malnatis | 42.002163 | -87.725566 | Pizza     |

+--------------+-----------+------------+-----------+

1 row in set (0.001 sec)

===================

MariaDB [datajs]> SELECT name, latitude, longitude,

->     JSON_VALUE(attr, '$.details.foodType') AS food_type

-> FROM locations ;

+--------------+-----------+------------+-----------+

| name         | latitude  | longitude  | food_type |

+--------------+-----------+------------+-----------+

| Lou Malnatis | 42.002163 | -87.725566 | Pizza     |

| Cloud Gate   | 41.882657 | -87.623304 | NULL      |

+--------------+-----------+------------+-----------+

2 rows in set (0.001 sec)

==================

Index Creating

==================

MariaDB [datajs]> ALTER TABLE locations ADD COLUMN 

->     food_type VARCHAR(25) AS (JSON_VALUE(attr, '$.details.foodType')) VIRTUAL;

Query OK, 0 rows affected (0.009 sec)

Records: 0  Duplicates: 0  Warnings: 0

MariaDB [datajs]> CREATE INDEX foodtypes ON locations(food_type);

Query OK, 0 rows affected (0.008 sec)

Records: 0  Duplicates: 0  Warnings: 0

MariaDB [datajs]> select id from locations;

+----+

| id |

+----+

|  2 |

|  1 |

+----+

2 rows in set (0.001 sec)

MariaDB [datajs]> UPDATE locations

-> SET attr = JSON_INSERT(attr,'$.nickname','The Bean')

-> WHERE id = 1;

Query OK, 1 row affected (0.002 sec)

Rows matched: 1  Changed: 1  Warnings: 0

MariaDB [datajs]> UPDATE locations

->     SET attr = JSON_INSERT(attr,

->                              '$.foodTypes',

->         JSON_ARRAY('Asian', 'Mexican'))

-> WHERE id = 1;

Query OK, 1 row affected (0.002 sec)

Rows matched: 1  Changed: 1  Warnings: 0

================================

[boris@fedora35server ~]$ mysql -u root -p

Enter password: 

Welcome to the MariaDB monitor.  Commands end with ; or \g.

Your MariaDB connection id is 13

Server version: 10.5.13-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

=================================

MariaDB [(none)]> use datajs;

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A

Database changed

MariaDB [datajs]> desc locations;

+-----------+--------------+------+-----+---------+-------------------+

| Field     | Type         | Null | Key | Default | Extra             |

+-----------+--------------+------+-----+---------+-------------------+

| id        | int(11)      | NO   | PRI | NULL    | auto_increment    |

| name      | varchar(100) | NO   |     | NULL    |                   |

| type      | char(1)      | NO   |     | NULL    |                   |

| latitude  | decimal(9,6) | NO   |     | NULL    |                   |

| longitude | decimal(9,6) | NO   |     | NULL    |                   |

| attr      | longtext     | YES  |     | NULL    |                   |

| food_type | varchar(25)  | YES  | MUL | NULL    | VIRTUAL GENERATED |

+-----------+--------------+------+-----+---------+-------------------+

7 rows in set (0.002 sec)

MariaDB [datajs]> SELECT name, latitude, longitude,

->     JSON_VALUE(attr, '$.details.foodType') AS food_type

-> FROM locations

-> WHERE type = 'R';

+--------------+-----------+------------+-----------+

| name         | latitude  | longitude  | food_type |

+--------------+-----------+------------+-----------+

| Lou Malnatis | 42.002163 | -87.725566 | Pizza     |

+--------------+-----------+------------+-----------+

1 row in set (0.001 sec)

MariaDB [datajs]> SELECT name, latitude, longitude,

->     JSON_VALUE(attr, '$.details.foodType') AS food_type

-> FROM locations

-> ;

+--------------+-----------+------------+-----------+

| name         | latitude  | longitude  | food_type |

+--------------+-----------+------------+-----------+

| Lou Malnatis | 42.002163 | -87.725566 | Pizza     |

| Cloud Gate   | 41.882657 | -87.623304 | NULL      |

+--------------+-----------+------------+-----------+

2 rows in set (0.001 sec)

MariaDB [datajs]> select * from locations ;

+----+--------------+------+-----------+------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+

| id | name         | type | latitude  | longitude  | attr                                                                                                                                                                                                                                                             | food_type |

+----+--------------+------+-----------+------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+

|  1 | Lou Malnatis | R    | 42.002163 | -87.725566 | {"details": {"foodType": "Pizza", "menu": "https://www.loumalnatis.com/our-menu"}, "favorites": [{"description": "Pepperoni deep dish", "price": 18.75}, {"description": "The Lou", "price": 24.75}], "nickname": "The Bean", "foodTypes": ["Asian", "Mexican"]} | Pizza     |

|  2 | Cloud Gate   | A    | 41.882657 | -87.623304 | {"category": "Landmark", "lastVisitDate": "11/10/2019"}                                                                                                                                                                                                          | NULL      |

+----+--------------+------+-----------+------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+

2 rows in set (0.001 sec)


MariaDB [datajs]> SELECT name,latitude,longitude,

    ->     JSON_QUERY(attr, '$.details') AS details

    -> FROM locations

    -> WHERE type = 'R';

+--------------+-----------+------------+-----------------------------------------------------------------------+

| name         | latitude  | longitude  | details                                                               |

+--------------+-----------+------------+-----------------------------------------------------------------------+

| Lou Malnatis | 42.002163 | -87.725566 | {"foodType": "Pizza", "menu": "https://www.loumalnatis.com/our-menu"} |

+--------------+-----------+------------+-----------------------------------------------------------------------+

1 row in set (0.001 sec)


MariaDB [datajs]> SELECT name,latitude,longitude,

    ->     JSON_QUERY(attr, '$.favorites') AS favorites

    -> FROM locations

    -> WHERE type = 'R';

+--------------+-----------+------------+------------------------------------------------------------------------------------------------------+

| name         | latitude  | longitude  | favorites                                                                                            |

+--------------+-----------+------------+------------------------------------------------------------------------------------------------------+

| Lou Malnatis | 42.002163 | -87.725566 | [{"description": "Pepperoni deep dish", "price": 18.75}, {"description": "The Lou", "price": 24.75}] |

+--------------+-----------+------------+------------------------------------------------------------------------------------------------------+

1 row in set (0.000 sec)


Introduction to Random Forests in Scikit-Learn (sklearn)

Классификатор случайного леса — это то, что известно как алгоритм ансамбля. Причина этого в том, что он одновременно использует несколько экземпляров другого алгоритма для поиска результата. Помните, что деревья решений склонны к переобучению. Однако вы можете решить эту проблему, просто посадив больше деревьев!

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

Модели машинного обучения имеют некоторые ограничения:

Они не могут работать с отсутствующими данными, и

Они не могут работать с категориальными строковыми данными.











Изучив информацию, возвращаемую методом .info(), вы увидите, что обе эти проблемы существуют в наборе данных. Чтобы иметь возможность использовать этот набор данных для классификации, вам сначала нужно найти способы работы с отсутствующими и категоричными данными. Это именно то, что вы узнаете в следующих двух разделах руководства.

==================================

Мы внедрим класс SimpleImputer из sklearn.impute и numpy.

Мы создалим экземпляр объекта SimpleImputer, ища отсутствующие значения, представленные np.NaN, и попросим Scikit-Learn использовать «среднее» в качестве своей стратегии. Это означает, что любые значения np.NaN будут вменены средним значением столбцов.Затем мы используем метод .fit() и передаем столбец. Наконец, мы используем метод .transform() для передачи вмененных значений в соответствующие столбцы.

====================

(.env) [boris@fedora35server DATARF]$ cat missDataRF1.py

# Loading the Penguins Dataset from Seaborn

import seaborn as sns

import pandas as pd

from sklearn.impute import SimpleImputer

import numpy as np


df = sns.load_dataset('penguins')

print(df.head())

print("Seeing missing data")

print("=========================")

print(df.isnull().sum())


# Create a SimpleImputer Class

imputer = SimpleImputer(missing_values=np.NaN, strategy='mean')


# Fit the columns to the object

columns = ['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g']

imputer=imputer.fit(df[columns])


# Transform the DataFrames column with the fitted data

df[columns]=imputer.transform(df[columns])

print(df.head())

print("Seeing missing data again")

print("=========================")

print(df.isnull().sum())

(.env) [boris@fedora35server DATARF]$ python missDataRF1.py

  species     island  bill_length_mm  ...  flipper_length_mm  body_mass_g     sex

0  Adelie  Torgersen            39.1  ...              181.0       3750.0    Male

1  Adelie  Torgersen            39.5  ...              186.0       3800.0  Female

2  Adelie  Torgersen            40.3  ...              195.0       3250.0  Female

3  Adelie  Torgersen             NaN  ...                NaN          NaN     NaN

4  Adelie  Torgersen            36.7  ...              193.0       3450.0  Female


[5 rows x 7 columns]

Seeing missing data

=========================

species               0

island                0

bill_length_mm        2

bill_depth_mm         2

flipper_length_mm     2

body_mass_g           2

sex                  11

dtype: int64

  species     island  bill_length_mm  ...  flipper_length_mm  body_mass_g     sex

0  Adelie  Torgersen        39.10000  ...         181.000000  3750.000000    Male

1  Adelie  Torgersen        39.50000  ...         186.000000  3800.000000  Female

2  Adelie  Torgersen        40.30000  ...         195.000000  3250.000000  Female

3  Adelie  Torgersen        43.92193  ...         200.915205  4201.754386     NaN

4  Adelie  Torgersen        36.70000  ...         193.000000  3450.000000  Female


[5 rows x 7 columns]

Seeing missing data again

=========================

species               0

island                0

bill_length_mm        0

bill_depth_mm         0

flipper_length_mm     0

body_mass_g           0

sex                  11

dtype: int64















Полная версия кода

(.env) [boris@fedora35server DATARF]$ cat missDataRF.py

# Loading the Penguins Dataset from Seaborn

import seaborn as sns

import pandas as pd

from sklearn.impute import SimpleImputer

import numpy as np

from sklearn.model_selection import train_test_split

from sklearn.ensemble import RandomForestClassifier

import matplotlib.pyplot as plt

from sklearn.tree import plot_tree

from sklearn.preprocessing import OneHotEncoder


df = sns.load_dataset('penguins')

print(df.head())

print("Seeing missing data")

print("=========================")

print(df.isnull().sum())


# Create a SimpleImputer Class

imputer = SimpleImputer(missing_values=np.NaN, strategy='mean')


# Fit the columns to the object

columns = ['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g']

imputer=imputer.fit(df[columns])


# Transform the DataFrames column with the fitted data

df[columns]=imputer.transform(df[columns])

print(df.head())

print("Seeing missing data again")

print("=========================")

print(df.isnull().sum())


#  Dropping missing records in the sex column

df = df.dropna(subset=['sex'])

print(df.isnull().sum())


df['sex int'] = df['sex'].map({'Male': 0, 'Female': 1})

print(df['island'].unique())


# One-hot Encoding the Island Feature

one_hot = OneHotEncoder()

encoded = one_hot.fit_transform(df[['island']])

df[one_hot.categories_[0]] = encoded.toarray()

# Drop unneeded columns

df = df.drop(columns=['island', 'sex'])


# Splitting the data and creating a model

X = df.iloc[:, 1:]

y = df['species']

X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.3, random_state=100)

forest = RandomForestClassifier(n_estimators=100, random_state=100)

forest.fit(X_train,y_train)

predictions = forest.predict(X_test)

# Another import

from sklearn import metrics

print("Accuracy:",metrics.accuracy_score(y_test, predictions))

# Plotting results

fig = plt.figure(figsize=(15, 10))

plot_tree(forest.estimators_[0],

          feature_names=X.columns,

          class_names=df['species'].unique(),

          filled=True, rounded=True)

plt.show()





























Reference

Tuesday, April 12, 2022

Dive into linear regression via gradient descent Python

 Линейная регрессия с использованием градиентного спуска

Градиентный спуск — один из наиболее распространенных методов, используемых для оптимизации различных выпуклых функций в машинном обучении. Поскольку мы знаем, что функция стоимости аналогична функции стоимости (с разницей в 1/2 раза), данной в методе наименьших квадратов, мы будем использовать градиентный спуск для решения проблемы. Мы должны минимизировать функцию стоимости, чтобы найти значение Theta на линии регрессии.

Метод градиентного спуска можно представить следующим образом:

































*******

Code 1

*******

(.env) [boris@fedora35server LINEAREGRS]$ cat gradientDesc1.py

import numpy as np

from matplotlib import pyplot as plt

from PIL import Image

# Function for cost function

def cost(z,theta,y):

    m,n=z.shape;

    htheta = z.dot(theta.transpose())

    cost = ((htheta - y)**2).sum()/(2.0 * m)

    return cost;


def gradient_descent(z,theta,alpha,y,itr):

    cost_arr=[]

    m,n=z.shape;

    count=0;

    htheta = z.dot(theta.transpose())

    while count<itr:

        htheta = z.dot(theta.transpose())

        a=(alpha/m)

        # Using temporary variables for simultaneous updation of variables

        temp0=theta[0,0]-a*(htheta-y).sum();

        temp1=theta[0,1]-a*((htheta-y)*(z[::,1:])).sum();

        theta[0,0]=temp0;

        theta[0,1]=temp1;

        cost_arr.append(float(cost(z,theta,y)));

        count+=1;

    cost_log = np.array(cost_arr);

    plt.plot(np.linspace(0, itr, itr, endpoint=True), cost_log)

    plt.xlabel("No. of iterations")

    plt.ylabel("Error Function value")

    # plt.show()

    fig = plt.gcf()

    fig.savefig('fig1.jpg')

    return theta;


x = np.array([[0], [1],[2], [3], [4], [5], [6], [7], [8], [9]]) 

y = np.array([[11], [13], [12], [15], [17], [18], [18], [19], [20], [22]]) 

m,n=x.shape;

z=np.ones((m,n+1),dtype=int);

z[::,1:]=x;

theta=np.array([[21,2]],dtype=float)

theta_minimised=gradient_descent(z,theta,0.01,y,10000)

print("theta_minimised =",theta_minimised)

new_x=np.array([1,11])

predicted_y=new_x.dot(theta_minimised.transpose())

print(round(predicted_y[0],4))

im = Image.open('fig1.jpg')

im.show()
















Код из https://www.educative.io/edpresso/a-deep-dive-into-linear-regression-3-way-implementation

изменен ( не прнинципиально, мне так удобней )




dive into linear regression (3-way implementation) Python

 Code 1

(.env) [boris@fedora35server LINEAREGRS]$ cat linearRegr1.py

import numpy as np 

import matplotlib.pyplot as plt 

def estimate_coef(x, y): 

    n = np.size(x) 

    m_x, m_y = np.mean(x), np.mean(y) 

    SS_xy = np.sum(y*x) - n*m_y*m_x 

    SS_xx = np.sum(x*x) - n*m_x*m_x 

    theta_1 = SS_xy / SS_xx 

    theta_0 = m_y - theta_1*m_x 

    return(theta_0, theta_1) 

def plot_regression_line(x, y, theta): 

    plt.scatter(x, y, color = "b",marker = "o", s = 30) 

    y_pred = theta[0] + theta[1]*x 

    plt.plot(x, y_pred, color = "r") 

    plt.xlabel('x') 

    plt.ylabel('y') 

    plt.show() 

x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 

y = np.array([11 ,13, 12, 15, 17, 18, 18, 19, 20, 22]) 

theta = estimate_coef(x, y) 

print("Estimated coefficients:\ntheta_0 = {} \ntheta_1 = {}".format(theta[0], theta[1])) 

plot_regression_line(x, y, theta) 

print(round(theta[0]+ theta[1]*11,4))
































References

Monday, April 11, 2022

Cross-validation: evaluating estimator performance

 При оценке различных настроек («гиперпараметров») для оценщиков, таких как параметр C, который должен быть установлен вручную для SVM, все еще существует риск переобучения в тестовом наборе, поскольку параметры можно настраивать до тех пор, пока оценщик не будет работать оптимально. Таким образом, знания о тестовом наборе могут «просочиться» в модель, а метрики оценки больше не сообщают о производительности обобщения. Чтобы решить эту проблему, еще одна часть набора данных может быть представлена ​​в виде так называемого «проверочного набора»: обучение продолжается на обучающем наборе, после чего выполняется оценка на проверочном наборе, и когда эксперимент кажется успешным , окончательную оценку можно выполнить на тестовом наборе.

Однако, разбивая доступные данные на три набора, мы резко сокращаем количество выборок, которые можно использовать для обучения модели, а результаты могут зависеть от конкретного случайного выбора пары наборов (обучение, проверка).

Решением этой проблемы является процедура, называемая перекрестной проверкой (сокращенно CV). Тестовый набор по-прежнему должен храниться для окончательной оценки, но проверочный набор больше не нужен при выполнении CV. В базовом подходе, называемом k-fold CV, обучающая выборка разбивается на k меньших наборов (другие подходы описаны ниже, но в целом следуют тем же принципам). Для каждой из k «складок» выполняется следующая процедура:

Модель обучается с использованием складок в качестве обучающих данных;

полученная модель проверяется на оставшейся части данных (т. Е. Она используется в качестве тестового набора для вычисления показателя производительности, такого как точность).

Мера производительности, о которой сообщает k-кратная перекрестная проверка, представляет собой среднее значение значений, вычисленных в цикле. Этот подход может быть дорогостоящим в вычислительном отношении, но не тратит слишком много данных (как в случае фиксации произвольного набора проверки), что является большим преимуществом в таких задачах, как обратный вывод, когда количество выборок очень мало.






















Самый простой способ использовать перекрестную проверку — вызвать вспомогательную функцию cross_val_score для оценщика и набора данных.
В следующем примере показано, как оценить точность линейной машины опорных векторов ядра в наборе данных радужной оболочки путем разделения данных, подгонки модели и вычисления оценки 5 раз подряд (каждый раз с разными разделениями):

(.env) [boris@fedora35server CROSSVL]$ cat crossVal1.py
from sklearn.model_selection import cross_val_score
from sklearn import svm
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn import datasets


X, y = datasets.load_iris(return_X_y=True)
clf = svm.SVC(kernel='linear', C=1, random_state=42)
scores = cross_val_score(clf, X, y, cv=5)
print("%0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))

(.env) [boris@fedora35server CROSSVL]$ python crossVal1.py
0.98 accuracy with a standard deviation of 0.02






























Другой пример

(.env) [boris@fedora35server CROSSVL]$ cat crossVal2.py
import pandas as pd
import numpy as np
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.ensemble import RandomForestClassifier
from sklearn import svm
from sklearn.model_selection import cross_val_score

#read csv file
data  =  pd.read_csv("./Iris.csv")
#Create Dependent and Independent Datasets based on our Dependent #and Independent features
X  = data[['SepalLengthCm','SepalWidthCm','PetalLengthCm']]
y= data['Species']
model = svm.SVC()
accuracy = cross_val_score(model, X, y, scoring='accuracy', cv = 10)
print(accuracy)

#get the mean of each fold 
print("Accuracy of Model with Cross Validation is:",accuracy.mean() * 100)

(.env) [boris@fedora35server CROSSVL]$ python crossVal2.py
[0.93333333 0.93333333 1.         1.         0.93333333 0.8
 0.93333333 0.93333333 1.         1.        ]
Accuracy of Model with Cross Validation is: 94.66666666666667






















Таким образом, перекрестная проверка — это процедура, используемая для предотвращения переобучения и оценки навыков модели на новых данных.
Существуют общие тактики, которые вы можете использовать для выбора значения k для вашего набора данных.
Существуют часто используемые варианты перекрестной проверки, такие как послойная и повторная, которые доступны в scikit-learn.

References