Библиотека Morpheus предназначена для облегчения разработки высокопроизводительного аналитического программного обеспечения, включающего большие наборы данных для анализа как в автономном режиме, так и в реальном времени на виртуальной машине Java (JVM). Библиотека написана на Java 8 с широким использованием лямбда-выражений, но доступна для всех языков JVM.
Подробную документацию с примерами смотрите здесь.
По своей сути Morpheus предоставляет универсальную двумерную структуру табличных данных , эффективно использующую память, называемую DataFrame
, аналогичную той, которая впервые была популяризирована в R. Хотя динамически типизированные языки научных вычислений, такие как R, Python и Matlab, отлично подходят для проведения исследований, они не очень хороши. подходит для крупномасштабных производственных систем, поскольку их становится чрезвычайно сложно поддерживать и опасно рефакторить. Библиотека Morpheus пытается сохранить мощь и универсальность концепции DataFrame
, обеспечивая при этом гораздо более типобезопасный и самоописываемый набор интерфейсов, что должно значительно упростить разработку, поддержку и масштабирование сложного кода.
Еще одним преимуществом библиотеки Morpheus является то, что она чрезвычайно хорошо масштабируется на многоядерных процессорных архитектурах, учитывая мощные возможности многопоточности виртуальной машины Java. Многие операции с Morpheus DataFrame
можно легко выполнять параллельно, просто вызывая функцию parallel()
для объекта, с которым вы хотите работать, во многом аналогично Java 8 Streams. Внутри эти параллельные реализации основаны на платформе Fork & Join, и для определенных типов операций наблюдается почти линейное улучшение производительности по мере добавления ядер ЦП.
Morpheus DataFrame
— это структура хранилища столбцов, в которой каждый столбец представлен Array
Morpheus, существует множество реализаций, включая плотные, разреженные версии и версии с отображением в памяти. Массивы Morpheus оптимизированы и, где это возможно, поддерживаются примитивными собственными массивами Java (даже для таких типов, как LocalDate
, LocalDateTime
и т. д.), поскольку они гораздо более эффективны с точки зрения хранения, доступа и сбора мусора. Arrays
Morpheus с отображением в памяти, хотя и являются экспериментальными, позволяют создавать очень большие DataFrames
с использованием хранилища вне кучи, поддерживаемого файлами.
Хотя полный набор функций Morpheus DataFrame
все еще развивается, уже существует множество мощных API, позволяющих с легкостью выполнять сложные преобразования и аналитические операции. Существуют стандартные функции для вычисления сводной статистики, выполнения различных типов линейных регрессий, применения анализа главных компонентов (PCA), и это лишь некоторые из них. DataFrame
индексируется как по строкам, так и по столбцам, что позволяет эффективно сортировать , нарезать , группировать и агрегировать данные по любой оси.
Morpheus также стремится предоставить стандартный механизм для загрузки наборов данных от различных поставщиков данных. Мы надеемся, что этот API будет принят сообществом для расширения каталога поддерживаемых источников данных. В настоящее время реализованы поставщики, позволяющие загружать данные из Quandl, Федеральной резервной системы, Всемирного банка, Yahoo Finance и Google Finance.
Рассмотрим набор данных о характеристиках автомобилей, доступный здесь. Код ниже загружает эти CSV-данные в Morpheus DataFrame
, фильтрует строки, чтобы включить только те транспортные средства, у которых отношение мощности к весу> 0,1 (где вес конвертируется в килограммы), затем добавляет столбец для записи относительной эффективности между шоссе и городской пробег (MPG), сортирует строки по этому вновь добавленному столбцу в порядке убывания и, наконец, записывает преобразованный результат в файл CSV.
DataFrame . read (). csv ( options -> {
options . setResource ( "http://zavtech.com/data/samples/cars93.csv" );
options . setExcludeColumnIndexes ( 0 );
}). rows (). select ( row -> {
double weightKG = row . getDouble ( "Weight" ) * 0.453592d ;
double horsepower = row . getDouble ( "Horsepower" );
return horsepower / weightKG > 0.1d ;
}). cols (). add ( "MPG(Highway/City)" , Double . class , v -> {
double cityMpg = v . row (). getDouble ( "MPG.city" );
double highwayMpg = v . row (). getDouble ( "MPG.highway" );
return highwayMpg / cityMpg ;
}). rows (). sort ( false , "MPG(Highway/City)" ). write (). csv ( options -> {
options . setFile ( "/Users/witdxav/cars93m.csv" );
options . setTitle ( "DataFrame" );
});
Этот пример демонстрирует функциональную природу API Morpheus, где многие возвращаемые типы методов на самом деле являются DataFrame
и, следовательно, допускают такую форму цепочки методов. В этом примере методы csv()
, select()
, add()
и sort()
возвращают кадр. В некоторых случаях тот же кадр, с которым работает метод, или в других случаях фильтр или неполная копия кадра, над которым работает метод. Первые 10 строк преобразованного набора данных в этом примере выглядят следующим образом: новый добавленный столбец отображается в крайнем правом углу кадра.
Индекс | Производитель | Модель | Тип | Мин.цена | Цена | Макс.Цена | MPG.city | MPG.шоссе | Подушки безопасности | Привод | Цилиндры | Размер двигателя | Лошадиные силы | об/мин | Оборот на милю | Человек.транс.доступно | Емкость топливного бака | Пассажиры | Длина | Колесная база | Ширина | Поворот.круг | Заднее сиденье | Камера хранения | Вес | Происхождение | Сделать | MPG (трасса/город) | -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- 9 | Кадиллак | ДеВиль | Большой | 33.0000 | 34,7000 | 36,3000 | 16 | 25 | Только водитель | Фронт | 8 | 4,9000 | 200 | 4100 | 1510 | Нет | 18.0000 | 6 | 206 | 114 | 73 | 43 | 35 | 18 | 3620 | США | Кадиллак ДеВиль | 1,5625 | 10 | Кадиллак | Севилья | Средний размер | 37,5000 | 40.1000 | 42,7000 | 16 | 25 | Водитель и пассажир | Фронт | 8 | 4,6000 | 295 | 6000 | 1985 | Нет | 20.0000 | 5 | 204 | 111 | 74 | 44 | 31 | 14 | 3935 | США | Кадиллак Севилья | 1,5625 | 70 | Олдсмобиль | Восемьдесят восемь | Большой | 19,5000 | 20,7000 | 21,9000 | 19 | 28 | Только водитель | Фронт | 6 | 3,8000 | 170 | 4800 | 1570 | Нет | 18.0000 | 6 | 201 | 111 | 74 | 42 | 31,5 | 17 | 3470 | США | Олдсмобиль Восемьдесят Восемь | 1,47368421 | 74 | Понтиак | Жар-птица | Спортивный | 14.0000 | 17,7000 | 21.4000 | 19 | 28 | Водитель и пассажир | Задний | 6 | 3,4000 | 160 | 4600 | 1805 | Да | 15,5000 | 4 | 196 | 101 | 75 | 43 | 25 | 13 | 3240 | США | Понтиак Жар-Птица | 1.47368421 | 6 | Бьюик | ЛеСабре | Большой | 19,9000 | 20,8000 | 21,7000 | 19 | 28 | Только водитель | Фронт | 6 | 3,8000 | 170 | 4800 | 1570 | Нет | 18.0000 | 6 | 200 | 111 | 74 | 42 | 30,5 | 17 | 3470 | США | Бьюик ЛеСабр | 1.47368421 | 13 | Шевроле | Камаро | Спортивный | 13,4000 | 15.1000 | 16,8000 | 19 | 28 | Водитель и пассажир | Задний | 6 | 3,4000 | 160 | 4600 | 1805 | Да | 15,5000 | 4 | 193 | 101 | 74 | 43 | 25 | 13 | 3240 | США | Шевроле Камаро | 1.47368421 | 76 | Понтиак | Бонневиль | Большой | 19,4000 | 24.4000 | 29,4000 | 19 | 28 | Водитель и пассажир | Фронт | 6 | 3,8000 | 170 | 4800 | 1565 | Нет | 18.0000 | 6 | 177 | 111 | 74 | 43 | 30,5 | 18 | 3495 | США | Понтиак Бонневиль | 1,47368421 | 56 | Мазда | RX-7 | Спортивный | 32,5000 | 32,5000 | 32,5000 | 17 | 25 | Только водитель | Задний | ротационный | 1,3000 | 255 | 6500 | 2325 | Да | 20.0000 | 2 | 169 | 96 | 69 | 37 | NA | NA | 2895 | не из США | Мазда RX-7 | 1.47058824 | 18 | Шевроле | Корвет | Спортивный | 34,6000 | 38.0000 | 41,5000 | 17 | 25 | Только водитель | Задний | 8 | 5,7000 | 300 | 5000 | 1450 | Да | 20.0000 | 2 | 179 | 96 | 74 | 43 | NA | NA | 3380 | США | Шевроле Корвет | 1.47058824 | 51 | Линкольн | Город_Автомобиль | Большой | 34,4000 | 36.1000 | 37,8000 | 18 | 26 | Водитель и пассажир | Задний | 8 | 4,6000 | 210 | 4600 | 1840 | Нет | 20.0000 | 6 | 219 | 117 | 77 | 45 | 31,5 | 22 | 4055 | США | Линкольн Таун_Автомобиль | 1.44444444 |
API Morpheus включает интерфейс регрессии, позволяющий подогнать данные к линейной модели с использованием OLS, WLS или GLS. В приведенном ниже коде используется тот же набор данных автомобиля, что и в предыдущем примере, и выполняется регрессия лошадиных сил по EngineSize . В примере кода результаты модели распечатываются в стандартный формат вывода, как показано ниже, а затем создается точечная диаграмма с четко отображаемой линией регрессии.
//Load the data
DataFrame < Integer , String > data = DataFrame . read (). csv ( options -> {
options . setResource ( "http://zavtech.com/data/samples/cars93.csv" );
options . setExcludeColumnIndexes ( 0 );
});
//Run OLS regression and plot
String regressand = "Horsepower" ;
String regressor = "EngineSize" ;
data . regress (). ols ( regressand , regressor , true , model -> {
System . out . println ( model );
DataFrame < Integer , String > xy = data . cols (). select ( regressand , regressor );
Chart . create (). withScatterPlot ( xy , false , regressor , chart -> {
chart . title (). withText ( regressand + " regressed on " + regressor );
chart . subtitle (). withText ( "Single Variable Linear Regression" );
chart . plot (). style ( regressand ). withColor ( Color . RED ). withPointsVisible ( true );
chart . plot (). trend ( regressand ). withColor ( Color . BLACK );
chart . plot (). axes (). domain (). label (). withText ( regressor );
chart . plot (). axes (). domain (). format (). withPattern ( "0.00;-0.00" );
chart . plot (). axes (). range ( 0 ). label (). withText ( regressand );
chart . plot (). axes (). range ( 0 ). format (). withPattern ( "0;-0" );
chart . show ();
});
return Optional . empty ();
});
=============================================== ========================================== Результаты линейной регрессии =============================================== ========================================== Модель: OLS R-квадрат: 0,5360 Наблюдения: 93 R-квадрат (скорректированный): 0,5309. Модель DF: 1 F-статистика: 105.1204 Остатки DF: 91 F-статистика (вероятность): 1,11E-16 Стандартная ошибка: 35,8717 Время выполнения (миллис) 52 Дурбин-Ватсон: 1,9591 =============================================== ========================================== Индекс | ПАРАМЕТР | СТАНД_ОШИБКА | Т_СТАТ | P_ЗНАЧ | CI_LOWER | CI_UPPER | -------------------------------------------------- -------------------------------------------- Перехват | 45.2195 | 10.3119 | 4,3852 | 3.107Э-5 | 24,736 | 65.7029 | Размер двигателя | 36,9633 | 3,6052 | 10.2528 | 7.573Е-17 | 29,802 | 44.1245 | =============================================== ==========================================
Доступ ко всем записям о сделках с жилой недвижимостью в Великобритании с 1995 года по сегодняшний день возможен с помощью инициативы правительства Великобритании по открытым данным. Данные представлены в формате CSV и содержат множество столбцов, включая такую информацию, как дата транзакции, уплаченная цена, полный адрес (включая почтовый индекс), тип недвижимости, тип аренды и т. д.
Давайте начнем с написания функции для загрузки этих CSV-файлов из корзин Amazon S3, и, поскольку они хранятся по одному файлу в год, мы предоставляем соответствующую параметризованную функцию. Учитывая требования нашего анализа, нет необходимости загружать все столбцы в файле, поэтому ниже мы выбираем чтение только столбцов с индексами 1, 2, 4 и 11. Кроме того, поскольку файлы не включают заголовок , мы переименовываем столбцы во что-то более значимое, чтобы сделать последующий доступ немного более понятным.
/**
* Loads UK house price from the Land Registry stored in an Amazon S3 bucket
* Note the data does not have a header, so columns will be named Column-0, Column-1 etc...
* @param year the year for which to load prices
* @return the resulting DataFrame, with some columns renamed
*/
private DataFrame < Integer , String > loadHousePrices ( Year year ) {
String resource = "http://prod.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-%s.csv" ;
return DataFrame . read (). csv ( options -> {
options . setResource ( String . format ( resource , year . getValue ()));
options . setHeader ( false );
options . setCharset ( StandardCharsets . UTF_8 );
options . setIncludeColumnIndexes ( 1 , 2 , 4 , 11 );
options . getFormats (). setParser ( "TransactDate" , Parser . ofLocalDate ( "yyyy-MM-dd HH:mm" ));
options . setColumnNameMapping (( colName , colOrdinal ) -> {
switch ( colOrdinal ) {
case 0 : return "PricePaid" ;
case 1 : return "TransactDate" ;
case 2 : return "PropertyType" ;
case 3 : return "City" ;
default : return colName ;
}
});
});
}
Ниже мы используем эти данные для расчета средней номинальной цены (без учета инфляции) квартиры за каждый год с 1995 по 2014 год для подмножества крупнейших городов Великобритании. В нефильтрованном наборе данных за период с 1993 по 2014 год содержится около 20 миллионов записей, и хотя загрузка и анализ занимает довольно много времени (около 3,5 ГБ данных), Морфеус выполняет аналитическую часть кода примерно за 5 секунд (не считая время загрузки) на стандартном Apple Macbook Pro, купленном в конце 2013 года. Обратите внимание, как мы используем параллельную обработку для загрузки и обработки данных, вызывая results.rows().keys().parallel()
.
//Create a data frame to capture the median prices of Apartments in the UK'a largest cities
DataFrame < Year , String > results = DataFrame . ofDoubles (
Range . of ( 1995 , 2015 ). map ( Year :: of ),
Array . of ( "LONDON" , "BIRMINGHAM" , "SHEFFIELD" , "LEEDS" , "LIVERPOOL" , "MANCHESTER" )
);
//Process yearly data in parallel to leverage all CPU cores
results . rows (). keys (). parallel (). forEach ( year -> {
System . out . printf ( "Loading UK house prices for %s... n " , year );
DataFrame < Integer , String > prices = loadHousePrices ( year );
prices . rows (). select ( row -> {
//Filter rows to include only apartments in the relevant cities
final String propType = row . getValue ( "PropertyType" );
final String city = row . getValue ( "City" );
final String cityUpperCase = city != null ? city . toUpperCase () : null ;
return propType != null && propType . equals ( "F" ) && results . cols (). contains ( cityUpperCase );
}). rows (). groupBy ( "City" ). forEach ( 0 , ( groupKey , group ) -> {
//Group row filtered frame so we can compute median prices in selected cities
final String city = groupKey . item ( 0 );
final double priceStat = group . colAt ( "PricePaid" ). stats (). median ();
results . data (). setDouble ( year , city , priceStat );
});
});
//Map row keys to LocalDates, and map values to be percentage changes from start date
final DataFrame < LocalDate , String > plotFrame = results . mapToDoubles ( v -> {
final double firstValue = v . col (). getDouble ( 0 );
final double currentValue = v . getDouble ();
return ( currentValue / firstValue - 1d ) * 100d ;
}). rows (). mapKeys ( row -> {
final Year year = row . key ();
return LocalDate . of ( year . getValue (), 12 , 31 );
});
//Create a plot, and display it
Chart . create (). withLinePlot ( plotFrame , chart -> {
chart . title (). withText ( "Median Nominal House Price Changes" );
chart . title (). withFont ( new Font ( "Arial" , Font . BOLD , 14 ));
chart . subtitle (). withText ( "Date Range: 1995 - 2014" );
chart . plot (). axes (). domain (). label (). withText ( "Year" );
chart . plot (). axes (). range ( 0 ). label (). withText ( "Percent Change from 1995" );
chart . plot (). axes (). range ( 0 ). format (). withPattern ( "0.##'%';-0.##'%'" );
chart . plot (). style ( "LONDON" ). withColor ( Color . BLACK );
chart . legend (). on (). bottom ();
chart . show ();
});
Процентное изменение номинальных медианных цен на квартиры в выбранных городах показано на графике ниже. Это показывает, что Лондон не пострадал от какого-либо падения номинальных цен на жилье в результате глобального финансового кризиса (GFC), однако не все города Великобритании оказались такими устойчивыми. Что немного удивительно, так это то, что в период с 2003 по 2006 год в некоторых менее богатых северных городах наблюдался более высокий уровень роста курса по сравнению с Лондоном. Следует отметить, что, хотя в Лондоне не произошло никакого снижения номинальных цен, определенно произошла довольно серьезная коррекция евро и доллара США, поскольку фунт стерлингов сильно обесценился по отношению к этим валютам во время GFC.
Визуализация данных в Morpheus DataFrames
упрощается благодаря простому API абстракции диаграмм с адаптерами, поддерживающими как JFreeChart, так и Google Charts (а также другие, которые будут следовать по многочисленным просьбам). Эта конструкция позволяет создавать интерактивные диаграммы Java Swing, а также диаграммы на основе браузера HTML5 с помощью одного и того же программного интерфейса. Дополнительные сведения о том, как использовать этот API, см. в разделе о визуализации здесь, а код — здесь.
Morpheus опубликован в Maven Central, поэтому его можно легко добавить в качестве зависимости в выбранный вами инструмент сборки. Кодовая база в настоящее время разделена на 5 репозиториев, что позволяет разрабатывать каждый модуль независимо. Основной модуль, удачно названный morpheus-core, является базовой библиотекой, от которой зависят все остальные модули. Различные артефакты Maven следующие:
Морфеус Ядро
Базовая библиотека, содержащая массивы Morpheus, DataFrames и другие ключевые интерфейсы и реализации.
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-core</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Визуализация Морфеуса
Компоненты визуализации для отображения DataFrames
в диаграммах и таблицах.
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-viz</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Морфеус Квандл
Адаптер для загрузки данных из Quandl
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-quandl</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Морфеус Гугл
Адаптер для загрузки данных из Google Finance
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-google</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Морфеус Yahoo
Адаптер для загрузки данных из Yahoo Finance
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-yahoo</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Форум вопросов и ответов создан с помощью групп Google и доступен здесь.
Доступ к Morpheus Javadocs можно получить онлайн здесь.
Здесь можно получить доступ к серверу сборки непрерывной интеграции, который собирает код после каждого слияния.
Morpheus выпускается под лицензией Apache Software Foundation версии 2.