YANCEES: Yet ANother Configurable Extensible Event Service |
| Concepts: | Overview | | Versatility
|
| System: | Implementation | | Documentation | |
Download
| |
CVS
repository |
In this project, we study the use of configurable and extensible software architectures to provide versatility to event notification services (a.k.a. publish/subscribe infrastructures). Its goal is to study the trade-offs involved in the construction and use of such infrastructures. Current industrial infrastrucutres strive to support the variability of different application domains through the applicaiton of different strategies: 1) Build new publish/subscribe infrastructures from scratch; 2) Use minimal core infrastructures such as Siena, Elvin or Scribe; 3) Use one-size-fits-all infrastructures as CORBA-NS or READY. More recently, a new class of publish/subscribe infrastructures that relies on compositional approaches have been created. Examples include FACET, GREEN and YANCEES. YANCEES is an extensible and configurable component framework that allows the construction of application-specific publish/subscribe infrastructures. Examples of such extensions include subscriptions with event correlation, different notification policies (push and pull), protocols for mobile applications integration, ad-hoc P2P network protocols; event persistence services, security and others. YANCEES configurability and extensibility is achieved by the use of extensible languages and plug-in oriented architectures, along main publish/subscribe design dimensions (event, publication, subscription, notification, protocol and resource models). In other words, versatility is achieved by:
The key insight on YANCEES is the separation of languages (subscription, notification and protocol) and their implementation (plug-ins), for each design dimension of a notification server. Those two aspects are then dynamically combined through the user of parsers and configuration managers, allowing dynamic allocation of resources and the runtime installation of new featuers to the infrastructure. Another insight is that, different languages and adapters can be used, simultaneously, allowing the integration of different publish/subscripe networks and event sources.1. An open, extensible and configurable architecture where plug-ins can be installed
2. Extensible event, notification, subscription and protocol languages
Internally, YANCEES is a framework that combines:
By combining different features impelmented by means of plug-ins, filters and protocols, YANCEES allows the built of application-specific notifications services. The general approach is described in the following figure.1. Dynamic parsers that allocate plug-ins to cope with the provided subscription and notification commands/policies.
2. Input and output filters
3. Special pluggable services
4. Configuration managers and builders
5. Adapters that can support existing publish/subscribe networks
6. A core that integrates those featues.
Many
distributed applications interact by the exchange of events.
For the point of view of the applications, notification services
provide a distributed implementation of the
implicit
invocation pattern, intermediating the communication between
publishers of
information and their consumers (subscribers). In such systems, at one
side, publishers produce pieces of information that are sent (or
published) to a logically-centralized service (a.k.a.notification
service); whereas information consumers (or subscribers)
express their interest on those pieces of informationby means of
subscriptions. Subscriptions are usually represented
as logical expressions on the content, type and/or order of events.
More
advanced systems allow more expressive constructs, permitting the
correlation of events, for example. This
approach allows the insulation between producers and consumers of
information, which copes with the dynamism, heterogeneity, scalability
and changeability requirements of many distributed applications. The
publish/subscribe
communication style also facilitates the
combination and integration of information from different sources as
they get produced.
For having such characteristics, event notification servers have been used as the basic communication infrastructure in a large set of application domains. These domains include user and software monitoring, testing, CSCW, awareness, workflow management systems, mobile applications, enterprise integration, software engineering environments and so on. Due to their increasing popularity and the peculiarities of each application domain, different functinality has been demanded from such infrastructures.
Even though a big set of commercial and research notification services are currently available, they are not usually designed for evolution and configurabilty. In other words, they provide poor programability, configurability and dynamic change support. From a software engineer perspective, before YANCEES, developers had basically three options when using notification services in their applications:
Use what we call “one-size-fits-all” or monolythic approaches, such as adopted by CORBA Notification Service (CORBA-NS), READY or JMS. These infrastructures strive to address new applications requirements by providing, in advance, a very comprehensive set of features. Besides their inability to scale (down) to more simple applications, they are not designed with extensibility in mind. As applications evolve demand new requirements, the infrastructure itself cannot evolve to address these new requirements.
Use
specialized notification services, designed for
different application domains. Also motivated by the poor versatility
of current standardized solutions such as CORBA-NS or JMS, many
notification services have been
developed to provide novel but specific
functionalities demanded by different applications.
Examples of such specialized systems include KHRONIKA and CASSIUS
which
are specially designed to support groupware and awareness applications;
or YEAST and GEM which are specialized in advanced event
processing for local networks
applications, or JEDI, which first studies strategies to support
mobility in publish/subscribe systems. Moreover, those infrastructures
are usually implement incompatible and
non-programmable solutions, which in a big organization result in
incompatibilty and interoperability problems, since different
notification services may need to co-exist and inter-operate.
In a third and emerging category, notification services such as Siena or Elvin can be used. They provide a minimal core of features in a content-based network. A compromise between expressiveness and performance is achieved in favor of its scalability, providing an optimized content-based event routing network. Due to their emphases on fast and scalable routing of events, they provide a simple but efficient filtering language. Configurability programability is not their primary concerns, being the responsibility of the application to implement extra services such as event persistence, and advanced event correlation, for example.
Configurabilty: Specialization versus Generalization. Therefore, in the development of event driven distributed applications, developers face the dilemma of specialization versus generalization: to use a generalized infrastructure, that can support and integrate different applications, but that may not provide all the necessary functionality (in terms of subscription language, notification policies and other points of variance in notification services); or to use specialized event-based infrastructures for each application domain, having “the right tool for the right problem”, but loosing the uniformity and integration of a common solution. For example, in the development of awareness tools, one can use either a comprehensive solution such as CORBA Notification Service (CORBA-NS) that provides a large set of features and services, or a domain specific solution such as CASSIUS, that provides services for awareness applications. That’s important to note that infrastructures as CORBA-NS, even though are very comprehensive in its set of features, do not provide specific support for all the requirements of many specific application domains. For example, in the case of awareness applications, CORBA-NS does not provide event source hierarchy representation, browsing and discovery. CASSIUS, on the other hand, provides these features and allows the easy discovery of information sources and the subscription for events from different source components. If CORBA-NS were used in an awareness application demanding this feature, this service would need to be implemented by the system developers, in the application layer.
Processing
distribution.
Another problem
of the currently available event-based infrastructures is the poor
support for
selection and customization of where to execute the event
processing computation.
Few notification services, such as READY, provide this support. READY
extends the CORBA-NS, allowing
subscription processing to be done in the client side.
Infrastructure Evolution. Moreover, current event-based infrastructures lack mechanisms that allow the programming (or evolution) of their functionality. The only extension mechanism is usually the (understanding and) change of the notification service source code, or the implementation of the missing functionality as part of the application itself. Additionally, due to restrictions in their event or subscription models, the addition of new functionality may become a very time consuming and difficult task.
Scalabilty. Different applications demand different functionality sets from the publish/subscribe infrastructure. Moreover, they have different hardware requirements, requiring the support for device-constrained configurations such as PDAs, or more demanding server-side applications. Hence, infrastructures that allow the scale (up and down) of their features to different application requirements are not available.
Interoperabilty.
Interoperability issues may also be a problem. In large organizations,
for example, if
multiple notification services are used in the support for different
application domains, their integration may be an
issue. They are usually
incompatible with one another and do not easily
interoperate, as a result of different event syntax and semantic, as
well as subscription formats.
Dynamism. In order
to be useful, the
configurability and programmabilty aspects need to be
provided in
a way that does not inerrupt the publish/subscribe infrastructure. This
requires the ability to
dinamically configure/upgrade the system as required by the
addplicatoins.
Usability.
Another important aspect to be considered is usabilty. A notification
service must be easy to use, in the point of view of the programmers,
providing services that allow, for example, the registration and
discovery of new event souces, as a way to enhance the subscription
ability of the service.
In order to design a generic and
programmable
framework, the application domain must be understood. This requirements
analysis must lay out the main activities involved in the process being
automated, allowing the generalization of the solution through the
implementation of an object-oriented framework. In other words, a
framework must provide extensibilty along the main dimension of the
problem being generalized.
In the YANCEES framework, the programability points are inspired in the framework proposed by (Rosenblum and Wolf 1997), which captures the most relevant dimensions (or models) in the design of publish/subscribe services. In this framework, the object model describes the components that receive notifications (subscribers) and generate events (publishers). The event model describes the representation and characteristics of the events; the notification model is concerned with the way the events are delivered to the subscribers; the observation model describes the mechanisms used to express interest in occurrences of events; the timing model is concerned with the casual and temporal relations between the events; the resource model defines where, in the distributed system architecture, the observation and notification computations are located, as well as how they are allocated and accounted; finally, the naming model is concerned with the location of objects, events, and subscriptions in the system.
For the implementation of YANCEES, we consider a variation of this model proposed by (Cugola, Nitto, and Fuggetta 2001), with the addition of a new model, the protocol. The protocol model is defined as a way to address the need to capture other forms of interaction with the notification service that goes beyond the common publish/subscribe interaction. This is necessary to incorporate the ability to handle different functionality other than the common publish and subscribe activities, and that require the interaction of the publish/subscribe core with other components of the system. Examples of protocols that may be necessary are: guaranteed delivery, mobility and roaming protocols, security, event source discovery and other possible interaction mechanisms with the service.
The YANCEES strategy to provide programability and configurability is to implement these models as programmable points along an object-oriented framework. An extension in this model is represented by programmable languages and protocols, whose implementation is provided by user-defined plug-ins, services and filters.
Currently, YANCEES is bult on top of a generic and replaceable pub/sub core. It provides an programability layer, that allows the use of different subscription, notification and event models with the development of plug-ins and language extensions (using XML).
As highlighted in the picture above, plug-ins are used to extend the event, notification, subscription and protocol models. These models are implemented as sets of languages defined in XML. Examples of plug-ins include event correlation expressions (sequence detection, rules, temporal and causality constructs), push and pull notification delivery policies, move-in/move-out protocol primitives to support mobile clients, event ontology and persistence services, and so on. An alternative picture of the overall approach is presented as follows.
In a high-level perspective, YANCEES provides a framework that combines plug-ins (in red) with language extensions (in orange), along the main event processing steps. Along with those dimensions, the protocol dimension is also provided, supporting the implementation of different interaction mechanisms with the server.
Under the hood, YANCEES provides a set of parsers that combine the extensions in the languages (which express the design models), with their semantic implementation, the plug-ins. Internally, the framework provides auxiliary extension points (filters, services and adapters) that are used to support the implementation of more complex policies and services. Adapters are used to provide interoperability and support for different publish/subscribe substrates (currently Siena and Elvin). As shown in the picture below, each parser (in orange) is responsible for extending different design dimensions of a publish/subscribe system (notification, protocol and subscription).
The
server-side architecture of
the YANCEES framework is presented as
follows.
The components of the system are divided in main categories: parsers (orange), auxiliary parser components (service manager and plug-in manager) and a component configuration manager (all in green), and the instances allocated by this set of components (plug-ins, filters and services in red).
Parsers (in orange) are used to dynamically allocate plug-ins in response to messages sent to YANCEES API, mainly publish and subscribe commands. They use plug-in instances (in red), that are dynamically allocated by the Plug-in manager (green). Plug-ins are hierarchically composed, forming evaluation trees that extend the basic functionality of the event dispatcher core. There is one parser to each main design dimension. Plug-in instances (in red) extend the core subscription and notification languages provided by the dispatcher adapter and the dispatcher component (in blue). Protocol plug-ins interact with the service instances and filters in response to protocol messages. The architecture configuration manager deals with the dynamic installation of components and their distribution between client an server side. The whole components can be installed in the client-side, moving event processing to the client, or can be shared in the server-side, an intermediate approach balancing the location of the plug-ins can also be achieved.
Filters and services are used to enhance the functionality provided by the plug-ins. Filters process events in the input and output streams of the framework, allowing the implementation of security policies, event type checking and so on; services can be used to provide access points to shared resources such as databases, data models, and so on.
The following figure shows the flow of messages in the system and the dynamic allocation of plug-ins in response to the messages provided (other components are omitted here for simplicity).
The
arrival of a message
(subscription arrow in the picture) triggers the appropriate
parser (Subscriber API), which identifies the XML tags of the message.
In the example
above, we assume a subscription message having two parts, a
subscription expression and a notification policy. The subscriber API
splits the message in their two components, forwarding each part to the
appropriate parser, in this case, the notification and the subscription
parsers. Each parser allocates plug-in
instances to handle the commands expressed in each part of the message.
An
evaluation tree, composed of plug-in instances, is then created, in
memory, to handle (or interpret) that particular
message. The tree is composed of notification and subscription nodes.
The leaves of this tree are special plug-ins that subscribe to events
in the dispatcher. Plug-is more close to the top of the tree perform
higher-level computations, such as event sequence detection. Once the
tree is parsed, events coming from the dispatcher that matches the
leaves subscriptions are processed, in a bottom-up approach, through
this structure, having the results sent to the subscriber as
notifications. Before delivering the notifications, the message passes
through output filters that enforce global policies in the system. Note
that while subscriptions are handled on demand, and are associated to
unique subscribers, filters are applied to all the events of the system.
The basic parsing algorithm, which is also applied to the notification, subscription and protocol models, is illustrated as follows (example with subscription language and parser):
Plug-ins associated to each tag of the message (subscription in our example) are allocated according to their disposition in the XML model (or document) where they are represented. Each plug-in instance handles one or more XML tags in the message being parsed. Events are sent upward in the subscription tree as the plug-ins expressions are evaluated.
The following table summarizes the event notification design dimensions supported by YANCEES and describes how to extend each one of them in our approach.
| DESIGN DIMENSION | HOW TO EXTEND/USE | EXAMPLE EXTENSIONS |
| Subscription Model | Extend
the subscription
language using XML; Provide plug-in implementations for each tag or the new set of tags; Use optional input or output filters and services. |
Event
correlation (sequence
detection, aggregation, rules) Primitives to support different dispatchers (Elvin, JMS, Siena CORBA-NS and others) Topic-based Channel-based |
| Publication Model | Allows the definition of filters that can intercept the publication of events, supporting global policies such as event type checking, or features such as event persistency and infrastructure protocols. | Type ckecing filter, repeated events filter, peer publish protocol interceptor. |
| Event Model | Extend
the event
representation in XML; Provide event dispatchers or dispatcher adapters to handle the event transport; Use optional input and output filters to enforce the event representation. |
Tuple-based
(attribute/value
pairs) Record-based Object-based Typed and un-typed events |
| Notification Model | Extend
the notification
language using XML; Provide plug-in implementations for each tag; implementing the notification policies; |
Push Periodic push Pull (with persistence) |
| Resource Model | Register
client and server
components using YANCEES configuration language,
which is interpreted by the configuration manager; |
Centralized, Partially-centralized, and Client-side configurations |
| Protocol Model | Define
protocol messages using
XML definitions Provide protocol plug-ins (client and server-side ones) to interpret and respond to the messages as they are received. |
Security
protocols Mobility Protocols Ad-hoc P2P networking protocols Configuration protocols |
The
configurabilty is provided by
YANCEES configuration language, which allows the selection of specific
plug-ins, filters and services to be used by each YANCEES instance. The
configuration file is provided when the service is started. Pluig-ins
interdependencies are checked and enforced by the configuration manager
at bootstrap time.
The
YANCEES prototype was
implemented and tested with Java (J2SDK1.4.2 and WSDP1.2 - which
provides XMLSchema validation support for the DOM parser). Currently,
the framework supports
the core mechanisms presented in the previous sections: input and
output filters, subscription, notification and protocol plug-ins, and
shared services, which can be configured at boot time with the use of
configuration description files. The system was ported to work using
Siena and
Elvin. It also supports our own fast topic-based dispatcher. The
communication between client stubs and the
server
is implemented using Java RMI protocol.
The
following plug-ins are
implemented: event sequence
detection, pull and push notification policies, polling protocol, a
simple
persistence service (in memory), a fast multiplexer dispatcher
(topic-based), and many other features.
The
service is multithreaded and
supports many simultaneous subscriptions and publications as
non-blocking operations. Input and output buffers were implemented to
improve overall throughput and performance.
In the
next releases of YANCEES,
we plan to improve the architecture with:
More current source code is available
in the CVS
repository.
Examples,
installation
instructions, user
manual and configurations are under construction at the YANCEES
manual page (under construction).
This
software is distributed
according to the following LICENSE.TXT.
This software was tested with J2SDK1.4.2
and
Java API for XML processing available at SUN
WSDP1.2 (web services pack) or higher.
For your
convenience, we provide some examples of YANCEES bundled in different
configurations. Those buldes include only a Yancees-SNAPSHOT.jar file
with the publisher and subscriber source codes. windows and
unix shell
scripts are provided as well as the configuration file used to
customize YANCEES.
Minimal YANCEES:
(August 2006 snapshot)
Configured with a fast switch core, the subscription language has only
one command, <require>,
which filters messages with the specified attribute name.
Siena YANCEES:
(August 2006 snapshot)
Configured with Siena as the routing core, it provides siena
configuration language with an extra sequence detector plug-in. This is
the version used in our example above.
June 8th 2004 - The third stable public release of the YANCEES prototype is available. Previous releases can be downloaded here. The Java implementation of the prototype can be downloaded as follows:
This version supports:
Roberto
Silveira Silva Filho
Irvine, CA. March 18th, 2004.
| 2007 - Silva Filho, R. S. and D. F. Redmiles. Managing Feature Interaction by Documenting and Enforcing Dependencies in Software Product Lines. in proceedings of the 9th International Conference on Feature Interactions in Software and Communication Systems. pp.39-54, Grenoble, France, September 3-5, 2007. |
| 2005 - Silva Filho
R. S., |
| 2005 - Silva Filho R. S., Redmiles D. F. A Survey of Versatility for Publish/Subscribe Infrastructures. Technical Report UCI-ISR-05-8. University of California, Irvine, May 2005. |
| 2004 - Silva Filho R. S., Redmiles D. F. Preserving Versatility in Event-based Middleware. Technical Report UCI-ISR-04-7. University of California, Irvine, October 2004. |
| 2004 - Silva Filho R. S., De Souza C. R. B., Redmiles D. F. Design and Experiments with YANCEES, a Versatile Publish-Subscirbe Service. Technical Report UCI-ISR-04-1. University of California, Irvine, April 2004. |
| 2003 - Silva Filho R. S., De Souza C. R. B., Redmiles D. F. The Design of a Configurable, Extensible and Dynamic Notification Service. in Proc. Second International Workshop on Distributed Event-Based Systems (DEBS'03), In conjunction with The ACM SIGMOD/PODS Conference, San Diego, CA, USA, June 8th, 2003. |
Additional materials |
| Slides from the ICFI 2007 conference are available here. |
| |
| Slides from the SEM 2005 workshop are available here. |
| Slides from the DEBS 2003 workshop are available here. |
| |
| YANCEES Flyer
presented in the
ISR Internal Workshop, June 2005 is available here. |
| |
| YANCEES Poster presented in the ISR Internal Workshop, June 2004 is available here. |
This research has been sponsored by NSF (grants 0133749, 0205724, 0326105, 0527729,0524033), an IBM Eclipse technology exchange grant, and the Intel Corporation.


Using Henrik Bengtsson’s future package, it is possible to evaluate modules asynchronously using various resources available to the user. For instance, evaluation of modules can be sequential, lazy, and/or parallelized (on multiple cores and/or on a cluster of machines).
# How many cores are available?
availableCores()
#> system
#> 4
"foo" %provides% {
future({
Sys.sleep(1L)
"Hello"
}) %plan% multicore
}
#> [2018-12-02T16:14:14 UTC] Defining 'foo' ... OK
"bar" %provides% {
future({
Sys.sleep(1L)
"World"
}) %plan% multicore
}
#> [2018-12-02T16:14:14 UTC] Defining 'bar' ... OK
"foobar" %requires% list(
f = "foo",
b = "bar"
) %provides% {
paste0(value(f), " ", value(b), "!")
}
#> [2018-12-02T16:14:14 UTC] Defining 'foobar' ... OK
system.time(print(make("foobar")))
#> [2018-12-02T16:14:14 UTC] Making 'foobar' ...
#> [2018-12-02T16:14:14 UTC] * Visiting and defining dependencies ...
#> [2018-12-02T16:14:14 UTC] * Constructing dependency graph ... OK
#> [2018-12-02T16:14:14 UTC] * Sorting 2 dependencies with 2 relations ... on 1 layer, OK
#> [2018-12-02T16:14:14 UTC] * Evaluating new and outdated dependencies ...
#> [2018-12-02T16:14:14 UTC] ** Evaluating #1/2 (layer #1/1): 'bar' ...
#> [2018-12-02T16:14:14 UTC] ** Evaluating #2/2 (layer #1/1): 'foo' ...
#> [2018-12-02T16:14:15 UTC] DONE ('foobar' in 1.1 secs)
#> [1] "Hello World!"
#> user system elapsed
#> 0.059 0.014 1.073
It is often interesting to parallelize an already existing module. modulr
comes with the handy futurize
function for this purpose. The following two examples illustrate the difference between sequential and lazy orchestrations of parallelized modules.
"foo" %provides% { Sys.sleep(1L); "Hello" }
#> [2018-12-02T16:14:15 UTC] Defining 'foo' ... OK
"bar" %provides% { Sys.sleep(1L); "World" }
#> [2018-12-02T16:14:15 UTC] Defining 'bar' ... OK
"foobar" %requires% list(
f = "foo",
b = "bar"
) %provides% {
paste0(f, " ", b, "!")
}
#> [2018-12-02T16:14:15 UTC] Defining 'foobar' ... OK
futurize("foo", strategy = multicore)
#> [2018-12-02T16:14:15 UTC] Defining 'foo/future' ... OK
futurize("bar", strategy = multicore)
#> [2018-12-02T16:14:15 UTC] Defining 'bar/future' ... OK
futurize(
"foobar",
name = "foobar/sequential",
dependencies = list(f = "foo/future", b = "bar/future"),
strategy = sequential
)
#> [2018-12-02T16:14:15 UTC] Defining 'foobar/sequential' ... OK
system.time(fb_sequential <- make("foobar/sequential"))
#> [2018-12-02T16:14:16 UTC] Making 'foobar/sequential' ...
#> [2018-12-02T16:14:16 UTC] * Visiting and defining dependencies ...
#> [2018-12-02T16:14:16 UTC] * Constructing dependency graph ... OK
#> [2018-12-02T16:14:16 UTC] * Sorting 2 dependencies with 2 relations ... on 1 layer, OK
#> [2018-12-02T16:14:16 UTC] * Evaluating new and outdated dependencies ...
#> [2018-12-02T16:14:16 UTC] ** Evaluating #1/2 (layer #1/1): 'bar/future' ...
#> [2018-12-02T16:14:16 UTC] ** Evaluating #2/2 (layer #1/1): 'foo/future' ...
#> [2018-12-02T16:14:17 UTC] DONE ('foobar/sequential' in 1.1 secs)
#> user system elapsed
#> 0.073 0.023 1.073
system.time(print(value(fb_sequential)))
#> [1] "Hello World!"
#> user system elapsed
#> 0.001 0.000 0.001
touch("foo/future")
#> [2018-12-02T16:14:17 UTC] Touching 'foo/future' ... OK
touch("bar/future")
#> [2018-12-02T16:14:17 UTC] Touching 'bar/future' ... OK
futurize(
"foobar",
name = "foobar/lazy",
dependencies = list(f = "foo/future", b = "bar/future"),
strategy = sequential,
lazy = TRUE
)
#> [2018-12-02T16:14:17 UTC] Defining 'foobar/lazy' ... OK
system.time(fb_lazy <- make("foobar/lazy"))
#> [2018-12-02T16:14:17 UTC] Making 'foobar/lazy' ...
#> [2018-12-02T16:14:17 UTC] * Visiting and defining dependencies ...
#> [2018-12-02T16:14:17 UTC] * Constructing dependency graph ... OK
#> [2018-12-02T16:14:17 UTC] * Sorting 2 dependencies with 2 relations ... on 1 layer, OK
#> [2018-12-02T16:14:17 UTC] * Evaluating new and outdated dependencies ...
#> [2018-12-02T16:14:17 UTC] ** Evaluating #1/2 (layer #1/1): 'bar/future' ...
#> [2018-12-02T16:14:17 UTC] ** Evaluating #2/2 (layer #1/1): 'foo/future' ...
#> [2018-12-02T16:14:17 UTC] DONE ('foobar/lazy' in 0.074 secs)
#> user system elapsed
#> 0.062 0.013 0.077
Sys.sleep(0.5)
system.time(print(value(fb_lazy)))
#> [1] "Hello World!"
#> user system elapsed
#> 0.011 0.009 0.420
Let us consider the following situation: most of your code rely on an old version of dplyr, say 0.4.1, and you want to migrate progressively to the latest version available on CRAN, say 0.7.0. How two different versions of dplyr can cohabitate in your ecosystem? Package isolation is an experimental feature allowing to isolate a portion of code, for example a module, from already loaded and attached packages.
Let us assume that dplyr_0.4.1 is installed in the current library paths:
library(dplyr) # version 0.4.1, installed in the library paths
#>
#> Attaching package: 'dplyr'
#> The following object is masked from 'package:stats':
#>
#> filter
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
print(sessionInfo())
#> R version 3.2.5 (2016-04-14)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Red Hat Enterprise Linux
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_DK.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] dplyr_0.4.1 memoise_1.1.0 future_1.10.0
#> [4] RColorBrewer_1.1-2 chorddiag_0.1.1 networkD3_0.2.13
#> [7] modulr_<IP_ADDRESS>08
#>
#> loaded via a namespace (and not attached):
#> [1] igraph_1.0.1 Rcpp_0.12.19 rstudioapi_<IP_ADDRESS>00
#> [4] knitr_1.19 magrittr_1.5 pooh_0.3-2
#> [7] stringr_1.3.1 globals_0.12.4 tools_3.2.5
#> [10] parallel_3.2.5 DBI_0.6-1 withr_2.1.2
#> [13] htmltools_0.3.6 yaml_2.1.16 assertthat_0.2.0
#> [16] rprojroot_1.3-2 digest_0.6.18 htmlwidgets_0.8
#> [19] codetools_0.2-14 evaluate_0.10.1 rmarkdown_1.8
#> [22] stringi_1.2.4 backports_1.1.2 listenv_0.7.0
This is the current code using dplyr_0.4.1, where arrange
applies by default to every element of the group:
cars %>%
group_by(speed) %>%
arrange(desc(dist)) %>%
ungroup %>%
head
#> Source: local data frame [6 x 2]
#>
#> speed dist
#> 1 4 10
#> 2 4 2
#> 3 7 22
#> 4 7 4
#> 5 8 16
#> 6 9 10
Let us isolate from all the loaded and attached packages (except the so-called base packages and a few others, like modulr itself, obviously):
isolate_from_packages()
#> [2018-12-02T16:14:18 UTC] Isolating from loaded and attached packages ...
#> [2018-12-02T16:14:18 UTC] * Ignoring base packages and 14 packages: 'assertthat', 'curl', 'devtools', 'digest', 'httr', 'jsonlite', 'knitr', 'memoise', 'modulr', 'pooh', 'rmarkdown', 'rstudioapi', 'stringi', 'stringr'.
#> [2018-12-02T16:14:18 UTC] * Forgetting 'dplyr' ... OK
#> [2018-12-02T16:14:19 UTC] * Forgetting 'future' ... OK
#> [2018-12-02T16:14:19 UTC] * Forgetting 'RColorBrewer' ... OK
#> [2018-12-02T16:14:19 UTC] * Forgetting 'chorddiag' ... OK
#> [2018-12-02T16:14:19 UTC] * Forgetting 'networkD3' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'igraph' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'Rcpp' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'magrittr' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'globals' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'DBI' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'withr' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'htmltools' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'lazyeval' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'yaml' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'rprojroot' ... OK
#> [2018-12-02T16:14:19 UTC] * Unloading 'htmlwidgets' ... OK
#> [2018-12-02T16:14:20 UTC] * Unloading 'codetools' ... OK
#> [2018-12-02T16:14:20 UTC] * Unloading 'evaluate' ... OK
#> [2018-12-02T16:14:20 UTC] * Unloading 'backports' ... OK
#> [2018-12-02T16:14:20 UTC] * Unloading 'listenv' ... OK
#> [2018-12-02T16:14:20 UTC] Isolated from loaded and attached packages.
This function returns a packages manifest containing all the necessary information to restore the situation. The last manifest produced by isolate_from_packages
is available from the .Last.packages_manifest
variable and is used by restore_packages
(see infra).
Let us now suppose that dplyr_0.7.0 and its dependencies have been installed in the dedicated “./new_lib” directory. We can then change the library path to point to ./new_lib
, and attach the packages as usual:
.libPaths("./new_lib")
library(dplyr) # version 0.7.0, installed in "./new_lib"
print(sessionInfo())
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
#> R version 3.2.5 (2016-04-14)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Red Hat Enterprise Linux
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_DK.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] dplyr_0.7.0 memoise_1.1.0 modulr_<IP_ADDRESS>08
#>
#> loaded via a namespace (and not attached):
#> [1] Rcpp_0.12.11 rstudioapi_<IP_ADDRESS>00 magrittr_1.5
#> [4] knitr_1.19 devtools_1.13.2 R6_2.2.1
#> [7] rlang_0.1.1 pooh_0.3-2 stringr_1.3.1
#> [10] tools_3.2.5 parallel_3.2.5 assertthat_0.2.0
#> [13] digest_0.6.18 tibble_1.3.3 glue_1.1.0
#> [16] evaluate_0.10 rmarkdown_1.8 stringi_1.2.4
Thus, the following code uses dplyr_0.7.0: a tibble is returned instead of a data frame and the verb arrange
requires a parameter (.by_group = TRUE
) to apply to every element of the group:
cars %>%
group_by(speed) %>%
arrange(desc(dist), .by_group = TRUE) %>%
ungroup %>%
head
#> # A tibble: 6 x 2
#> speed dist
#> <dbl> <dbl>
#> 1 4 10
#> 2 4 2
#> 3 7 22
#> 4 7 4
#> 5 8 16
#> 6 9 10
Finally, we can restore the previously attached and loaded packages (by default, the previous library paths are also restored), based by default on the last manifest:
restore_packages()
#> [2018-12-02T16:14:20 UTC] Restoring previously attached and loaded packages ...
#> [2018-12-02T16:14:20 UTC] * Restoring library paths ... OK
#> [2018-12-02T16:14:20 UTC] * Forgetting 'dplyr' ... OK
#> [2018-12-02T16:14:20 UTC] * Unloading 'Rcpp' ... OK
#> [2018-12-02T16:14:20 UTC] * Unloading 'magrittr' ... OK
#> [2018-12-02T16:14:21 UTC] * Unloading 'R6' ... OK
#> [2018-12-02T16:14:21 UTC] * Unloading 'rlang' ... OK
#> [2018-12-02T16:14:21 UTC] * Unloading 'tibble' ... OK
#> [2018-12-02T16:14:21 UTC] * Unloading 'glue' ... OK
#> [2018-12-02T16:14:21 UTC] * Unloading 'evaluate' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'listenv' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'backports' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'evaluate' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'codetools' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'htmlwidgets' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'rprojroot' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'yaml' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'lazyeval' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'htmltools' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'withr' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'DBI' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'globals' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'magrittr' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'Rcpp' ... OK
#> [2018-12-02T16:14:21 UTC] * Loading 'igraph' ... OK
#> [2018-12-02T16:14:21 UTC] * Attaching 'dplyr' at position 2 ... OK
#> [2018-12-02T16:14:21 UTC] * Attaching 'future' at position 4 ... OK
#> [2018-12-02T16:14:21 UTC] * Attaching 'RColorBrewer' at position 5 ... OK
#> [2018-12-02T16:14:21 UTC] * Attaching 'chorddiag' at position 6 ... OK
#> [2018-12-02T16:14:21 UTC] * Attaching 'networkD3' at position 7 ... OK
#> [2018-12-02T16:14:21 UTC] Previously attached and loaded packages restored.
print(sessionInfo())
#> R version 3.2.5 (2016-04-14)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Red Hat Enterprise Linux
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_DK.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] dplyr_0.4.1 memoise_1.1.0 future_1.10.0
#> [4] RColorBrewer_1.1-2 chorddiag_0.1.1 networkD3_0.2.13
#> [7] modulr_<IP_ADDRESS>08
#>
#> loaded via a namespace (and not attached):
#> [1] Rcpp_0.12.19 pillar_1.1.0 tools_3.2.5
#> [4] digest_0.6.18 evaluate_0.10.1 rlang_<IP_ADDRESS>
#> [7] igraph_1.0.1 DBI_0.6-1 rstudioapi_<IP_ADDRESS>00
#> [10] yaml_2.1.16 parallel_3.2.5 withr_2.1.2
#> [13] stringr_1.3.1 knitr_1.19 globals_0.12.4
#> [16] htmlwidgets_0.8 devtools_1.13.2 rprojroot_1.3-2
#> [19] listenv_0.7.0 pooh_0.3-2 rmarkdown_1.8
#> [22] magrittr_1.5 backports_1.1.2 codetools_0.2-14
#> [25] htmltools_0.3.6 assertthat_0.2.0 stringi_1.2.4
#> [28] lazyeval_0.2.0
The with_packages
function allows to isolate temporarily a portion of code, so that the previous example simply becomes:
with_verbosity(1L, with_packages("./new_lib", {
library(dplyr)
cars %>%
group_by(speed) %>%
arrange(desc(dist), .by_group = TRUE) %>%
ungroup %>%
as.data.frame(stringsAsFactors = FALSE) %>%
head
}))
#> [2018-12-02T16:14:21 UTC] Isolating from loaded and attached packages ...
#> [2018-12-02T16:14:24 UTC] Isolated from loaded and attached packages.
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
#> [2018-12-02T16:14:24 UTC] Restoring previously attached and loaded packages ...
#> [2018-12-02T16:14:25 UTC] Previously attached and loaded packages restored.
#> speed dist
#> 1 4 10
#> 2 4 2
#> 3 7 22
#> 4 7 4
#> 5 8 16
#> 6 9 10
When working with modules, it is sometimes useful to associate a specific library to an on-disk module with with_module_packages
or to a namespace of modules with with_namespace_packages
.
In the following example, with_module_packages
temporarily sets the library path to the sub-directory lib/x86_64-pc-linux-gnu-library/3.2
of the directory experimental/arranged_cars
.
# File: ./modules/experimental/arranged_cars.R
library(modulr)
"experimental/arranged_cars" %provides% {
with_verbosity(0L, with_module_packages({
if (!"dplyr" %in% rownames(installed.packages()))
install.packages("dplyr")
library(dplyr)
cars %>%
group_by(speed) %>%
arrange(desc(dist), .by_group = TRUE) %>%
ungroup %>%
as.data.frame(stringsAsFactors = FALSE) %>%
head
}))
}
make("experimental/arranged_cars")
#> [2018-12-02T16:14:25 UTC] Making 'experimental/arranged_cars' ...
#> [2018-12-02T16:14:25 UTC] * Visiting and defining dependencies ...
#> [2018-12-02T16:14:25 UTC] ** Defining 'experimental/arranged_cars' ... OK
#> [2018-12-02T16:14:25 UTC] * Constructing dependency graph ... OK
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
#> [2018-12-02T16:14:30 UTC] DONE ('experimental/arranged_cars' in 4.5 secs)
#> speed dist
#> 1 4 10
#> 2 4 2
#> 3 7 22
#> 4 7 4
#> 5 8 16
#> 6 9 10


#include <linux/hdreg.h> /* for HDIO_GETGEO */ #include <linux/fs.h> /* for BLKGETSIZE and BLKRRPART */
SCSI disks have a major device number of 8, and a minor device number of the form (16 * drive_number) + partition_number, where drive_number is the number of the physical drive in order of detection, and partition_number is as follows:
partition 0 is the whole drive
partitions 1-4 are the DOS "primary" partitions
partitions 5-8 are the DOS "extended" (or "logical") partitions
For example, /dev/sda will have major 8, minor 0, and will refer to all of the first SCSI drive in the system; and /dev/sdb3 will have major 8, minor 19, and will refer to the third DOS "primary" partition on the second SCSI drive in the system.
At this time, only block devices are provided. Raw devices have not yet been implemented.
struct hd_geometry { unsigned char heads; unsigned char sectors; unsigned short cylinders; unsigned long start; };
A pointer to this structure is passed as the ioctl(2) parameter.
The information returned in the parameter is the disk geometry of the drive as understood by DOS! This geometry is not the physical geometry of the drive. It is used when constructing the drive's partition table, however, and is needed for convenient operation of fdisk(1), efdisk(1), and lilo(1). If the geometry information is not available, zero will be returned for all of the parameters.
The SCSI ioctl(2) operations are also supported. If the ioctl(2) parameter is required, and it is NULL, then ioctl(2) will fail with the error EINVAL.


Start for free & get
attractive offers today !

Sign In
Sign Up
Sign Up
Or
Get Started
User Name
User Email
Password
Submit


Bookmark
Store your bookmarks online with us!
See what other user use!
Share your favorite links with others!
Not a member?
Member log in here:
Username:
Password:
Forgot your password?


| Programming with Ophis | ||
|---|---|---|
| <<< Previous | The basics | Next >>> |
This code includes constants that are both in decimal and in hex. It is also possible to specify constants in octal, binary, or with an ASCII character.
To specify decimal constants, simply write the number.
To specify hexadecimal constants, put a $ in front.
To specify octal constants, put a 0 (zero) in front.
To specify binary constants, put a % in front.
To specify ASCII constants, put an apostrophe in front.
There are other commands besides .byte and .word to specify data. In particular, the .dword command specifies four-byte values which some applications will find useful. Also, some linking formats (such as the SID format) have header data in big-endian (high byte first) format. The .wordbe and .dwordbe directives provide a way to specify multibyte constants in big-endian formats cleanly.
| <<< Previous | Home | Next >>> |
| Producing Commodore 64 programs | Up | Writing the actual code |


3 MLM, 3 levels: Nurse’s Stress Intervention
library(tidyverse)
library(haven) # read in SPSS dataset
library(furniture) # nice table1() descriptives
library(stargazer) # display nice tables: summary & regression
library(texreg) # Convert Regression Output to LaTeX or HTML Tables
library(psych) # contains some useful functions, like headTail
library(car) # Companion to Applied Regression
library(lme4) # Linear, generalized linear, & nonlinear mixed models
library(lmerTest) # Tests on lmer objects
library(sjstats) # ICC calculations
library(effects) # Effects for regression models
library(optimx) # Different optimizers to solve mlm's
3.1 Background
The text “Multilevel Analysis: Techniques and Applications, Third Edition” (Hox, Moerbeek, and Van de Schoot 2017) has a companion website which includes links to all the data files used throughout the book (housed on the book’s GitHub repository).
The following example is used through out Hox, Moerbeek, and Van de Schoot (2017)’s chapater 2.
From Appendix E:
The nurses.sav file contains three-level simulated data from a hypothetical study on stress in hospitals. The data are from nurses working in wards nested within hospitals. It is a cluster-randomized experiment. In each of 25 hospitals, four wards are selected and randomly assigned to an experimental and a control condition. In the experimental condition, a training program is offered to all nurses to cope with job-related stress. After the program is completed, a sample of about 10 nurses from each ward is given a test that measures job-related stress. Additional variables (covariates) are: nurse age (years), nurse experience (years), nurse gender (0=male, 1 = female), type of ward (0=general care, 1=special care), and hospital size (0=small, 1 = medium, 2=large). The data have been generated to illustrate three-level analysis with a random slope for the effect of the intervention.
Here the data is read in and the SPSS variables with labels are converted to \(R\) factors.
data_raw <- haven::read_sav("https://github.com/MultiLevelAnalysis/Datasets-third-edition-Multilevel-book/raw/master/chapter%202/Nurses/SPSS/Nurses.sav") %>%
haven::as_factor() # retain the labels from SPSS --> factor
3.1.1 Unique Identifiers
All standardized (starts with “Z”) and mean centered (starts with “C”) variables will be remove so that their creation may be shown later. A new indicator varible for nurses with be created by combining the hospital
, ward
, and nurse
indicators. Having a unique, distinct identifier variable for each of the units on lower (Level 1 and 2) levels is helpful for multilevel anlayses.
data_nurse <- data_raw %>%
dplyr::mutate(genderF = factor(gender,
labels = c("Male", "Female"))) %>% # apply factor labels
dplyr::mutate(id = paste(hospital, ward, nurse,
sep = "_") %>% # cunique id for each student
factor()) %>% # declare id is a factor
dplyr::mutate_at(vars(hospital, ward,
wardid, nurse), factor) %>% # declare to be factors
dplyr::mutate(age = age %>% as.character %>% as.numeric) %>% # declare to be numeric
dplyr::select(id, wardid, nurse, ward, hospital,
age, gender, genderF, experien,
wardtype, hospsize,
expcon, stress) # reduce variables included
tibble::glimpse(data_nurse)
Observations: 1,000
Variables: 13
$ id <fct> 1_1_1, 1_1_2, 1_1_3, 1_1_4, 1_1_5, 1_1_6, 1_1_7, 1_1_...
$ wardid <fct> 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 1...
$ nurse <fct> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16...
$ ward <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,...
$ hospital <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,...
$ age <dbl> 36, 45, 32, 57, 46, 60, 23, 32, 60, 45, 57, 47, 32, 4...
$ gender <dbl> 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1,...
$ genderF <fct> Male, Male, Male, Female, Female, Female, Female, Fem...
$ experien <dbl> 11, 20, 7, 25, 22, 22, 13, 13, 17, 21, 24, 24, 14, 13...
$ wardtype <fct> general care, general care, general care, general car...
$ hospsize <fct> large, large, large, large, large, large, large, larg...
$ expcon <fct> experiment, experiment, experiment, experiment, exper...
$ stress <dbl> 7, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 6, 5, 5, 6,...
3.1.2 Centering Variables
When variables are involved in an interaction, it may be advantageous to center the variables. Hox, Moerbeek, and Van de Schoot (2017) covers this in chapter 4.
To center categorical variables:
1. Convert then to integers, starting with zero: \(0, 1, \dots\)
2. Subtract the mean
data_nurse %>%
dplyr::mutate(expconN = as.numeric(expcon) - 1) %>% # Numeric Version of experimental condition
dplyr::mutate(hospsizeN = as.numeric(hospsize) - 1) %>% # Numeric Version of hospital size
dplyr::select(expcon, expconN, hospsize, hospsizeN) %>%
summary()
expcon expconN hospsize hospsizeN
control :496 Min. :0.000 small :374 Min. :0.000
experiment:504 1st Qu.:0.000 medium:476 1st Qu.:0.000
Median :1.000 large :150 Median :1.000
Mean :0.504 Mean :0.776
3rd Qu.:1.000 3rd Qu.:1.000
Max. :1.000 Max. :2.000
data_nurse <- data_nurse %>%
dplyr::mutate(expconN = as.numeric(expcon) - 1) %>% # Numeric Version of experimental condition
dplyr::mutate(hospsizeN = as.numeric(hospsize) - 1) %>% # Numeric Version of hospital size
dplyr::mutate(expconNG = expconN - 0.504) %>% # Grand-Mean Centered version of experimental condition
dplyr::mutate(hospsizeNG = hospsizeN - 0.776) # Grand-Mean Centered version of ehospital size
expconNG
expcon -0.504 0.496
control 496 0
experiment 0 504
hospsizeNG
hospsize -0.776 0.224 1.224
small 374 0 0
medium 0 476 0
large 0 0 150
3.2 Exploratory Data Analysis
3.2.1 Summarize Descriptive Statistics
<IP_ADDRESS> The stargazer
package
Most posters, journal articles, and reports start with a table of descriptive statistics. Since it tends to come first, this type of table is often refered to as Table 1. The stargazer()
function can be used to create such a table, but only for the entire dataset (Hlavac 2018). I haven’t been able to find a way to get it to summarize subsamples and compare them in the standard format.
# Knit to Website: type = "html"
# Knit to PDF: type = "latex"
# View on Screen: type = "text"
data_nurse %>%
data.frame() %>%
stargazer::stargazer(title = "Descriptive statistics, aggregate over entire sample",
header = FALSE,
type = "html")
| Statistic | N | Mean | St. Dev. | Min | Pctl(25) | Pctl(75) | Max |
| age | 1,000 | 43.005 | 12.042 | 23 | 33 | 53 | 64 |
| gender | 1,000 | 0.735 | 0.442 | 0 | 0 | 1 | 1 |
| experien | 1,000 | 17.057 | 6.042 | 1 | 13 | 21 | 38 |
| stress | 1,000 | 4.977 | 0.980 | 1 | 4 | 6 | 7 |
| expconN | 1,000 | 0.504 | 0.500 | 0 | 0 | 1 | 1 |
| hospsizeN | 1,000 | 0.776 | 0.689 | 0 | 0 | 1 | 2 |
| expconNG | 1,000 | -0.000 | 0.500 | -0.504 | -0.504 | 0.496 | 0.496 |
| hospsizeNG | 1,000 | -0.000 | 0.689 | -0.776 | -0.776 | 0.224 | 1.224 |
<IP_ADDRESS> The furniture
package
Tyson Barrett’s furniture package includes the extremely useful function table1()
which simplifies the common task of creating a stratified, comparative table of descriptive statistics. Full documentation can be accessed by executing ?furniture::table1
.
# Knit to Website: output = "html"
# Knit to PDF: output = "latex2"
# View on Screen: output = ""text", or "markdown", "html"
data_nurse %>%
furniture::table1(age, genderF, experien, wardtype, hospsize, hospsizeN, hospsizeNG,
splitby = ~ expcon, # var to divide sample by
test = TRUE, # test groups different?
type = "full", # give the test statistic
output = "html", # output for html
caption = "Compare Intervention groups on five main variables") # title
| control | experiment | Test | P-Value | |
|---|---|---|---|---|
| n = 496 | n = 504 | |||
| age | T-Test: 0.82 | 0.411 | ||
| 43.3 (11.6) | 42.7 (12.5) | |||
| genderF | Chi Square: 0.19 | 0.661 | ||
| Male | 135 (27.2%) | 130 (25.8%) | ||
| Female | 361 (72.8%) | 374 (74.2%) | ||
| experien | T-Test: 0.69 | 0.492 | ||
| 17.2 (5.8) | 16.9 (6.3) | |||
| wardtype | Chi Square: 0 | 1 | ||
| general care | 247 (49.8%) | 252 (50%) | ||
| special care | 249 (50.2%) | 252 (50%) | ||
| hospsize | Chi Square: 0.01 | 0.993 | ||
| small | 185 (37.3%) | 189 (37.5%) | ||
| medium | 237 (47.8%) | 239 (47.4%) | ||
| large | 74 (14.9%) | 76 (15.1%) | ||
| hospsizeN | T-Test: 0.01 | 0.992 | ||
| 0.8 (0.7) | 0.8 (0.7) | |||
| hospsizeNG | T-Test: 0.01 | 0.992 | ||
| 0.0 (0.7) | -0.0 (0.7) |
The t-test performed by the furniture::table1()
function will always assume indepent groups and that HOV is not violated. This may or may not be appropriate.
3.3 MLM: Null Model
In a Null, intercept-only, or Empty model, no predictors are included.
<IP_ADDRESS> Fit the Model
Fit the model to the data, with both ML and REML.
nurse_lmer_0_re <- lmerTest::lmer(stress ~ 1 + # Fixed Intercept for all nurses
(1|hospital/ward), # Random Intercepts for wards within hospitals
data = data_nurse,
REML = TRUE) # fit via REML (the default) for ICC calculations
nurse_lmer_0_ml <- lmerTest::lmer(stress ~ 1 + # Fixed Intercept for all nurses
(1|hospital/ward), # Random Intercepts for wards within hospitals
data = data_nurse,
REML = FALSE) # fit via ML for comparing FIXED effects inclusion
texreg::texreg(list(nurse_lmer_0_ml,
nurse_lmer_0_re),
custom.model.names = c("M0: Null, ML",
"M0: Null, REML"),
caption = "NULL Model: different estimation methods",
caption.above = TRUE,
single.row = TRUE)
| M0: Null, ML | M0: Null, REML | ||
|---|---|---|---|
| (Intercept) | 5.00 (0.11)*** | 5.00 (0.11)*** | |
| AIC | 1950.36 | 1952.95 | |
| BIC | 1969.99 | 1972.58 | |
| Log Likelihood | -971.18 | -972.48 | |
| Num. obs. | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | |
| Var: ward:hospital (Intercept) | 0.49 | 0.49 | |
| Var: hospital (Intercept) | 0.16 | 0.17 | |
| Var: Residual | 0.30 | 0.30 | |
| p < 0.001, p < 0.01, p < 0.05 |
3.4 Estimate the ICC
The ICC is calculated by dividing the between-group-variance (random intercept variance) by the total variance (i.e. sum of between-group-variance and within-group (residual) variance).
Groups Name Std.Dev.
ward:hospital (Intercept) 0.69916
hospital (Intercept) 0.41749
Residual 0.54887
Groups Name Variance Std.Dev.
ward:hospital (Intercept) 0.489 0.699
hospital (Intercept) 0.174 0.417
Residual 0.301 0.549
The sjstats
package has a few really helpful funcitons:
\[ \begin{align*} \text{hospitals} \rightarrow \; & \sigma^2_{v0} = 0.417^2 = 0.174\\ \text{wards within hospitals} \rightarrow \; & \sigma^2_{u0} = 0.699^2 = 0.489\\ \text{nurses within wards within hospitals} \rightarrow \; & \sigma^2_{e} = 0.549^2 = 0.301\\ \end{align*} \]
Intraclass Correlation (ICC) Formula, 3 level model - Davis and Scott Method \[ \overbrace{\rho_{mid}}^{\text{ICC}\atop\text{at level 2}} = \frac{\overbrace{\sigma^2_{u0}}^{\text{Random Intercept}\atop\text{Variance Level 2}}} {\underbrace{\sigma^2_{v0}+\sigma^2_{u0}+\sigma^2_{e}}_{\text{Total}\atop\text{Variance}}} \tag{Hox 2.16} \] \[ \overbrace{\rho_{top}}^{\text{ICC}\atop\text{ at level 3}} = \frac{\overbrace{\sigma^2_{u0}}^{\text{Random Intercept}\atop\text{Variance Level 3}}} {\underbrace{\sigma^2_{v0}+\sigma^2_{u0}+\sigma^2_{e}}_{\text{Total}\atop\text{Variance}}} \tag{Hox 2.17} \]
[1] 0.5072614
[1] 0.1804979
For more than two levels, the ‘sjstats::icc()’ function computes ICC’s by the Davis and Scott method AND combines the values. You will have to manually seperate the ICC values for each level.
# Intraclass Correlation Coefficient
Adjusted ICC: 0.688
Conditional ICC: 0.688
The proportion of variange in nurse stress level is 0.51 at the ward level and 0.18 at the hospital level.
To test if the three level model FOR MEANS is justified statistically, compare the null models with and without the nesting of wards in hospitals.
nurse_lmer_0_re_2level <- lmerTest::lmer(stress ~ 1 + (1|wardid), # each hospital contains several wards
data = data_nurse,
REML = TRUE) # fit via REML (the default) for ICC calculations
texreg::texreg(list(nurse_lmer_0_re_2level,
nurse_lmer_0_re),
custom.model.names = c("2 levels",
"3 levels"),
caption = "MLM: Two or Three Levels?",
caption.above = TRUE,
single.row = TRUE)
| 2 levels | 3 levels | ||
|---|---|---|---|
| (Intercept) | 5.00 (0.08)*** | 5.00 (0.11)*** | |
| AIC | 1958.43 | 1952.95 | |
| BIC | 1973.15 | 1972.58 | |
| Log Likelihood | -976.21 | -972.48 | |
| Num. obs. | 1000 | 1000 | |
| Num. groups: wardid | 100 | ||
| Var: wardid (Intercept) | 0.66 | ||
| Var: Residual | 0.30 | 0.30 | |
| Num. groups: ward:hospital | 100 | ||
| Num. groups: hospital | 25 | ||
| Var: ward:hospital (Intercept) | 0.49 | ||
| Var: hospital (Intercept) | 0.17 | ||
| p < 0.001, p < 0.01, p < 0.05 |
The deviance test or likelihood-ratio test shows that the inclusion of the nesting of wards within hospitals better explains the variance in nurse stress levels.
Data: data_nurse
Models:
nurse_lmer_0_re_2level: stress ~ 1 + (1 | wardid)
nurse_lmer_0_re: stress ~ 1 + (1 | hospital/ward)
Df AIC BIC logLik deviance Chisq Chi Df
nurse_lmer_0_re_2level 3 1958.4 1973.2 -976.21 1952.4
nurse_lmer_0_re 4 1953.0 1972.6 -972.48 1945.0 7.4738 1
Pr(>Chisq)
nurse_lmer_0_re_2level
nurse_lmer_0_re 0.00626 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
3.5 MLM: Add Fixed Effects
3.5.1 Fit the Model
Hox, Moerbeek, and Van de Schoot (2017), page 22:
“In this example, the variable
expcon
is of main interest, and the other variables are covariates. Their funciton is to control for differences between the groups, which can occur even if randomization is used, especially with small samples, and to explain variance in the outcome variable stress. To the extent that these variables successfully explain the variance, the power of the test for the effect ofexpcon
will be increased.”
nurse_lmer_1_ml <- lmerTest::lmer(stress ~ expcon + # experimental condition = CATEGORICAL FACTOR
age + gender + experien + # level 1 covariates
wardtype + # level 2 covariates
hospsize + # level 3 covariates, hospital size = CATEGORICAL FACTOR
(1|hospital/ward), # Random Intercepts for wards within hospitals
data = data_nurse,
REML = FALSE) # fit via ML for nested FIXED effects
texreg::texreg(list(nurse_lmer_0_ml,
nurse_lmer_1_ml),
custom.model.names = c("M0: null",
"M1: fixed pred"),
caption = "Nested Models: Fixed effects via ML",
caption.above = TRUE,
single.row = TRUE)
| M0: null | M1: fixed pred | ||
|---|---|---|---|
| (Intercept) | 5.00 (0.11)*** | 5.38 (0.18)*** | |
| expconexperiment | -0.70 (0.12)*** | ||
| age | 0.02 (0.00)*** | ||
| gender | -0.45 (0.03)*** | ||
| experien | -0.06 (0.00)*** | ||
| wardtypespecial care | 0.05 (0.12) | ||
| hospsizemedium | 0.49 (0.19)** | ||
| hospsizelarge | 0.90 (0.26)*** | ||
| AIC | 1950.36 | 1626.32 | |
| BIC | 1969.99 | 1680.30 | |
| Log Likelihood | -971.18 | -802.16 | |
| Num. obs. | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | |
| Var: ward:hospital (Intercept) | 0.49 | 0.33 | |
| Var: hospital (Intercept) | 0.16 | 0.10 | |
| Var: Residual | 0.30 | 0.22 | |
| p < 0.001, p < 0.01, p < 0.05 |
3.5.2 Assess Significance
Data: data_nurse
Models:
nurse_lmer_0_ml: stress ~ 1 + (1 | hospital/ward)
nurse_lmer_1_ml: stress ~ expcon + age + gender + experien + wardtype + hospsize +
nurse_lmer_1_ml: (1 | hospital/ward)
Df AIC BIC logLik deviance Chisq Chi Df Pr(>Chisq)
nurse_lmer_0_ml 4 1950.4 1970.0 -971.18 1942.4
nurse_lmer_1_ml 11 1626.3 1680.3 -802.16 1604.3 338.04 7 < 2.2e-16
nurse_lmer_0_ml
nurse_lmer_1_ml ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
It is clear that the inclusing of these fixed, main effects improves the model’s fit, but it is questionable that the type of ward is significant (Wald test is non-significant). Rather than test it directly, we will leave it in the model. This is common practice to show that an expected variable is not significant.
3.5.3 Centering Variables
Because we will will find that the experimental condition is moderated by hospital size (in other words there is a significant interaction between expcon
and hospsize
), Hox, Moerbeek, and Van de Schoot (2017) presents the models fit with centered values for these two variables. Let us see how this changes the model.
(1) Experimental Condition
Experimental conditon is a BINARY or two-level factor.
When it is alternatively coded as a numeric, continuous variable taking the values of zero (\(0\)) for the reference category and one (\(1\)) for the other category, the model estimates are exactly the same, including the paramters for the variables and the intercept, AND the model fit statistics.
When the numeric, continuous variable is further grand-mean centered by additionally subtraction the MEAN of the numberic version, the value of the intercept is the only estimate that changes.
nurse_lmer_1a_ml <- lmerTest::lmer(stress ~ expconN + # experimental condition = CONTINUOUS CODED 0/1
age + gender + experien +
wardtype +
hospsize + # hospital size = CATEGORICAL FACTOR
(1|hospital/ward),
data = data_nurse,
REML = FALSE)
nurse_lmer_1b_ml <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien +
wardtype +
hospsize + # hospital size = CATEGORICAL FACTOR
(1|hospital/ward),
data = data_nurse,
REML = FALSE)
texreg::texreg(list(nurse_lmer_1_ml,
nurse_lmer_1a_ml,
nurse_lmer_1b_ml),
custom.model.names = c("Factor",
"0 vs 1",
"Centered"),
caption = "MLM: Model 1 - Expereimental Condiditon Coding (2-levels)",
caption.above = TRUE,
single.row = TRUE)
texreg::htmlreg(list(nurse_lmer_1_ml,
nurse_lmer_1a_ml,
nurse_lmer_1b_ml),
custom.model.names = c("Factor",
"0 vs 1",
"Centered"),
caption = "MLM: Model 1 - Expereimental Condiditon Coding (2-levels)",
caption.above = TRUE,
single.row = TRUE)
| Factor | 0 vs 1 | Centered | ||
|---|---|---|---|---|
| (Intercept) | 5.38 (0.18)*** | 5.38 (0.18)*** | 5.03 (0.17)*** | |
| expconexperiment | -0.70 (0.12)*** | |||
| age | 0.02 (0.00)*** | 0.02 (0.00)*** | 0.02 (0.00)*** | |
| gender | -0.45 (0.03)*** | -0.45 (0.03)*** | -0.45 (0.03)*** | |
| experien | -0.06 (0.00)*** | -0.06 (0.00)*** | -0.06 (0.00)*** | |
| wardtypespecial care | 0.05 (0.12) | 0.05 (0.12) | 0.05 (0.12) | |
| hospsizemedium | 0.49 (0.19)** | 0.49 (0.19)** | 0.49 (0.19)** | |
| hospsizelarge | 0.90 (0.26)*** | 0.90 (0.26)*** | 0.90 (0.26)*** | |
| expconN | -0.70 (0.12)*** | |||
| expconNG | -0.70 (0.12)*** | |||
| AIC | 1626.32 | 1626.32 | 1626.32 | |
| BIC | 1680.30 | 1680.30 | 1680.30 | |
| Log Likelihood | -802.16 | -802.16 | -802.16 | |
| Num. obs. | 1000 | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | 25 | |
| Var: ward:hospital (Intercept) | 0.33 | 0.33 | 0.33 | |
| Var: hospital (Intercept) | 0.10 | 0.10 | 0.10 | |
| Var: Residual | 0.22 | 0.22 | 0.22 | |
| p < 0.001, p < 0.01, p < 0.05 |
(2) Hospital Size
Experimental conditon is a three-level factor.
When it is alternatively coded as a numeric, continuous variables taking the values of whole numbers, starting with zero (\(0, 1, 2, \dots\)), the model there will only be ONE parameter estimated instead of several (one less than the number of categories). This is becuase the levels are treated as being equally different from each other in terms of the outcome. This treats the effect of the categorical variable as if it is linear, which may or may not be appropriate. User beware!
When the numeric, continuous variable is further grand-mean centered by additionally subtraction the MEAN of the numberic version, the value of the intercept is the only estimate that changes.
nurse_lmer_1c_ml <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien +
wardtype +
hospsizeN + # hospital size = CONTINUOUS CODED 0/1
(1|hospital/ward),
data = data_nurse,
REML = FALSE)
nurse_lmer_1d_ml <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien +
wardtype +
hospsizeNG + # hospital size = CONTINUOUS GRAND-MEAN CENTERED
(1|hospital/ward),
data = data_nurse,
REML = FALSE)
texreg::texreg(list(nurse_lmer_1b_ml,
nurse_lmer_1c_ml,
nurse_lmer_1d_ml),
custom.model.names = c("Factor",
"0 vs 1",
"Centered"),
caption = "MLM: Model 1 - Hospital Coding (3-levels)",
caption.above = TRUE,
single.row = TRUE)
| Factor | 0 vs 1 | Centered | ||
|---|---|---|---|---|
| (Intercept) | 5.03 (0.17)*** | 5.04 (0.16)*** | 5.40 (0.12)*** | |
| expconNG | -0.70 (0.12)*** | -0.70 (0.12)*** | -0.70 (0.12)*** | |
| age | 0.02 (0.00)*** | 0.02 (0.00)*** | 0.02 (0.00)*** | |
| gender | -0.45 (0.03)*** | -0.45 (0.03)*** | -0.45 (0.03)*** | |
| experien | -0.06 (0.00)*** | -0.06 (0.00)*** | -0.06 (0.00)*** | |
| wardtypespecial care | 0.05 (0.12) | 0.05 (0.12) | 0.05 (0.12) | |
| hospsizemedium | 0.49 (0.19)** | |||
| hospsizelarge | 0.90 (0.26)*** | |||
| hospsizeN | 0.46 (0.12)*** | |||
| hospsizeNG | 0.46 (0.12)*** | |||
| AIC | 1626.32 | 1624.36 | 1624.36 | |
| BIC | 1680.30 | 1673.44 | 1673.44 | |
| Log Likelihood | -802.16 | -802.18 | -802.18 | |
| Num. obs. | 1000 | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | 25 | |
| Var: ward:hospital (Intercept) | 0.33 | 0.33 | 0.33 | |
| Var: hospital (Intercept) | 0.10 | 0.10 | 0.10 | |
| Var: Residual | 0.22 | 0.22 | 0.22 | |
| p < 0.001, p < 0.01, p < 0.05 |
3.6 MLM: Add Random Slope
Hox, Moerbeek, and Van de Schoot (2017), page 22:
“Although logically we can test if explanatory variables at the first level have random coefficients (slopes) at the second or third level, and if explanatory variables at teh second level have random coefficients (slopes) at the third level, these possibilities are not pursued. We DO test a model with a random coefficient (slope) for
expcon
at the third level, where there turns out to be significant slope variation.”
3.6.1 Fit the Model
nurse_lmer_1d_re <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien + # level 1 covariates
wardtype + # level 2 covariate
hospsizeNG + # level 3 covariate, hospital size = CONTINUOUS GRAND-MEAN CENTERED
(1|hospital/ward), # Random Intercepts for wards within hospitals
data = data_nurse,
REML = TRUE) # fit via REML for nested Random Effects
nurse_lmer_2_re <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien + # level 1 covariates
wardtype + # level 2 covariate
hospsizeNG + # level 3 covariate, hospital size = CONTINUOUS GRAND-MEAN CENTERED
(1|hospital/ward) + # Random Intercepts for wards within hospitals
(0 + expconNG|hospital), # RANDOM SLOPES for exp cond within hospital (does not vary witin a ward!)
data = data_nurse,
REML = TRUE) # fit via REML for nested Random Effects
texreg::texreg(list(nurse_lmer_1d_re,
nurse_lmer_2_re),
custom.model.names = c("M1: RI",
"M2: RIAS"),
caption = "Nested Models: Random Slope via REML",
caption.above = TRUE,
single.row = TRUE)
| M1: RI | M2: RIAS | ||
|---|---|---|---|
| (Intercept) | 5.40 (0.12)*** | 5.39 (0.11)*** | |
| expconNG | -0.70 (0.12)*** | -0.70 (0.18)*** | |
| age | 0.02 (0.00)*** | 0.02 (0.00)*** | |
| gender | -0.45 (0.03)*** | -0.45 (0.03)*** | |
| experien | -0.06 (0.00)*** | -0.06 (0.00)*** | |
| wardtypespecial care | 0.05 (0.12) | 0.05 (0.07) | |
| hospsizeNG | 0.46 (0.13)*** | 0.46 (0.13)*** | |
| AIC | 1659.89 | 1633.18 | |
| BIC | 1708.97 | 1687.17 | |
| Log Likelihood | -819.94 | -805.59 | |
| Num. obs. | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | |
| Var: ward:hospital (Intercept) | 0.34 | ||
| Var: hospital (Intercept) | 0.11 | 0.17 | |
| Var: Residual | 0.22 | 0.22 | |
| Var: ward.hospital (Intercept) | 0.11 | ||
| Var: hospital.1 expconNG | 0.69 | ||
| p < 0.001, p < 0.01, p < 0.05 |
3.6.2 Assess Significance
Data: data_nurse
Models:
nurse_lmer_1d_re: stress ~ expconNG + age + gender + experien + wardtype + hospsizeNG +
nurse_lmer_1d_re: (1 | hospital/ward)
nurse_lmer_2_re: stress ~ expconNG + age + gender + experien + wardtype + hospsizeNG +
nurse_lmer_2_re: (1 | hospital/ward) + (0 + expconNG | hospital)
Df AIC BIC logLik deviance Chisq Chi Df
nurse_lmer_1d_re 10 1659.9 1709.0 -819.94 1639.9
nurse_lmer_2_re 11 1633.2 1687.2 -805.59 1611.2 28.708 1
Pr(>Chisq)
nurse_lmer_1d_re
nurse_lmer_2_re 8.417e-08 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
The inclusion of a random slope effect for the experimental condition expcon
significantly improves the models’s fit, thus is should be retained.
3.7 MLM: Add Cross-Level Interaction
Hox, Moerbeek, and Van de Schoot (2017), page 22:
“The varying slope can be predicted by adding a cross-level interaction between the variables
expcon
andhospsize
. In view of this interaction, the variablesexpcon
andhospsize
have been centered on tehir overal means.”
3.7.1 Fit the Model
nurse_lmer_2_ml <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien + # level 1 covariates
wardtype + # level 2 covariate
hospsizeNG + # level 3 covariate, hospital size = CONTINUOUS GRAND-MEAN CENTERED
(1|hospital/ward) + # Random Intercepts for wards within hospitals
(0 + expconNG|hospital), # RANDOM SLOPES for exp cond within hospital (does not vary witin a ward!)
data = data_nurse,
REML = FALSE) # fit via ML for nested FIXED Effects
nurse_lmer_3_ml <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien + # level 1 covariates
wardtype + # level 2 covariate
hospsizeNG + # level 3 covariate, hospital size = CONTINUOUS GRAND-MEAN CENTERED
expconNG*hospsizeNG + # CROSS-LEVEL interaction
(1|hospital/ward) + # Random Intercepts for wards within hospitals
(0 + expconNG|hospital), # RANDOM SLOPES for exp cond within hospital (does not vary witin a ward!)
data = data_nurse,
REML = FALSE) # fit via ML for nested FIXED Effects
texreg::texreg(list(nurse_lmer_2_ml,
nurse_lmer_3_ml),
custom.model.names = c("M2: RAIS",
"M3: Xlevel Int"),
caption = "Nested Models: Fixed Cross-Level Interaction via ML",
caption.above = TRUE,
single.row = TRUE)
| M2: RAIS | M3: Xlevel Int | ||
|---|---|---|---|
| (Intercept) | 5.39 (0.11)*** | 5.39 (0.11)*** | |
| expconNG | -0.70 (0.18)*** | -0.72 (0.11)*** | |
| age | 0.02 (0.00)*** | 0.02 (0.00)*** | |
| gender | -0.46 (0.03)*** | -0.46 (0.03)*** | |
| experien | -0.06 (0.00)*** | -0.06 (0.00)*** | |
| wardtypespecial care | 0.05 (0.07) | 0.05 (0.07) | |
| hospsizeNG | 0.46 (0.12)*** | 0.46 (0.12)*** | |
| expconNG:hospsizeNG | 1.00 (0.16)*** | ||
| AIC | 1597.48 | 1576.07 | |
| BIC | 1651.47 | 1634.96 | |
| Log Likelihood | -787.74 | -776.03 | |
| Num. obs. | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | |
| Var: ward.hospital (Intercept) | 0.11 | 0.11 | |
| Var: hospital (Intercept) | 0.15 | 0.15 | |
| Var: hospital.1 expconNG | 0.66 | 0.18 | |
| Var: Residual | 0.22 | 0.22 | |
| p < 0.001, p < 0.01, p < 0.05 |
3.7.2 Assess Significance
Data: data_nurse
Models:
nurse_lmer_2_ml: stress ~ expconNG + age + gender + experien + wardtype + hospsizeNG +
nurse_lmer_2_ml: (1 | hospital/ward) + (0 + expconNG | hospital)
nurse_lmer_3_ml: stress ~ expconNG + age + gender + experien + wardtype + hospsizeNG +
nurse_lmer_3_ml: expconNG * hospsizeNG + (1 | hospital/ward) + (0 + expconNG |
nurse_lmer_3_ml: hospital)
Df AIC BIC logLik deviance Chisq Chi Df Pr(>Chisq)
nurse_lmer_2_ml 11 1597.5 1651.5 -787.74 1575.5
nurse_lmer_3_ml 12 1576.1 1635.0 -776.03 1552.1 23.413 1 1.307e-06
nurse_lmer_2_ml
nurse_lmer_3_ml ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
There is evidence that hospital size moderated the effect of the intervention. We will want to plot the estimated marginal means to interpret the meaning of this interaction.
3.8 Final Model
3.8.1 Fit the model
The final model should be fit via REML.
nurse_lmer_3_re <- lmerTest::lmer(stress ~ expconNG + # experimental condition = CONTINUOUS GRAND-MEAN CENTERED
age + gender + experien + # level 1 covariates
wardtype + # level 2 covariate
hospsizeNG + # level 3 covariate, hospital size = CONTINUOUS GRAND-MEAN CENTERED
expconNG*hospsizeNG + # CROSS-LEVEL interaction
(1|hospital/ward) + # Random Intercepts for wards within hospitals
(0 + expconNG|hospital), # RANDOM SLOPES for exp cond within hospital (does not vary witin a ward!)
data = data_nurse,
REML = TRUE) # fit via REML for final model
3.8.2 Table of Estimated Parameters
texreg::texreg(list(nurse_lmer_3_re),
custom.model.names = c("M3: Xlevel Int"),
caption = "Final Model: with REML",
caption.above = TRUE,
single.row = TRUE)
| M3: Xlevel Int | ||
|---|---|---|
| (Intercept) | 5.39 (0.11)*** | |
| expconNG | -0.72 (0.12)*** | |
| age | 0.02 (0.00)*** | |
| gender | -0.46 (0.03)*** | |
| experien | -0.06 (0.00)*** | |
| wardtypespecial care | 0.05 (0.07) | |
| hospsizeNG | 0.46 (0.13)*** | |
| expconNG:hospsizeNG | 1.00 (0.17)*** | |
| AIC | 1614.47 | |
| BIC | 1673.36 | |
| Log Likelihood | -795.23 | |
| Num. obs. | 1000 | |
| Num. groups: ward:hospital | 100 | |
| Num. groups: hospital | 25 | |
| Var: ward.hospital (Intercept) | 0.11 | |
| Var: hospital (Intercept) | 0.17 | |
| Var: hospital.1 expconNG | 0.20 | |
| Var: Residual | 0.22 | |
| p < 0.001, p < 0.01, p < 0.05 |
3.8.3 Visualization: Estimated Marginal Means Plot
Although there are many variables in this model, only two are involved in any interaction(s). For this reason, we will choose to display the estimated marginal means across only experimental condition and hospital size. For this illustration, all other continuous predictors are taken to be at their mean and categorical predictors at their reference category.
sjPlot::plot_model(nurse_lmer_3_re,
type = "pred",
terms = c("hospsizeNG [-0.776, 0.224, 1.224]",
"expconNG [-0.504, 0.496]")) +
scale_x_continuous(breaks = c(-0.776, 0.224, 1.224),
labels = c("small", "medium", "large")) +
scale_color_manual(labels = c("Control", "Intervention"),
values = c("red", "blue")) +
labs(title = "Multilevel Modeling of Hospital Nurse Stress Intervention",
subtitle = "Ribbons display 95% Confidene Intervals",
x = "Hospital Size",
y = "Estimated Marginal Mean\nNorse's Stress Score",
color = "Condition") +
theme_bw()
Get Estimated Marginal Means - default ‘nice’ predictor values:
Focal predictors: All combinations of…
expcon
categorical, both levelscontrol
andexperiment
hospsize
categorical, all three levelssmall
,medium
,large
Always followed by:
fit
estimated marginal meanse
standard error for the marginal meanlower
lower end of the 95% confidence interval around the estimated marginal meanupper
upper end of the 95% confidence interval around the estimated marginal mean
effects::Effect(focal.predictors = c("expconNG", "hospsizeNG"),
mod = nurse_lmer_3_re,
xlevels = list(expconNG = c(-0.504, 0.496),
hospsizeNG = c(-0.776, 0.224, 1.224))) %>%
data.frame()
expconNG hospsizeNG fit se lower upper
1 -0.504 -0.776 5.382989 0.1631593 5.062812 5.703166
2 0.496 -0.776 3.885142 0.1626411 3.565981 4.204302
3 -0.504 0.224 5.341652 0.1112640 5.123312 5.559992
4 0.496 0.224 4.842621 0.1109159 4.624964 5.060277
5 -0.504 1.224 5.300315 0.2138865 4.880593 5.720037
6 0.496 1.224 5.800100 0.2131921 5.381741 6.218459
effects::Effect(focal.predictors = c("expconNG", "hospsizeNG"),
mod = nurse_lmer_3_re,
xlevels = list(expconNG = c(-0.504, 0.496),
hospsizeNG = c(-0.776, 0.224, 1.224))) %>%
data.frame() %>%
dplyr::mutate(expcon = factor(expconNG + 0.504,
labels = c("Control", "Intervention"))) %>%
dplyr::mutate(hopsize = factor(hospsizeNG + 0.776,
labels = c("Small", "Medium", "Large")))
expconNG hospsizeNG fit se lower upper expcon
1 -0.504 -0.776 5.382989 0.1631593 5.062812 5.703166 Control
2 0.496 -0.776 3.885142 0.1626411 3.565981 4.204302 Intervention
3 -0.504 0.224 5.341652 0.1112640 5.123312 5.559992 Control
4 0.496 0.224 4.842621 0.1109159 4.624964 5.060277 Intervention
5 -0.504 1.224 5.300315 0.2138865 4.880593 5.720037 Control
6 0.496 1.224 5.800100 0.2131921 5.381741 6.218459 Intervention
hopsize
1 Small
2 Small
3 Medium
4 Medium
5 Large
6 Large
effects::Effect(focal.predictors = c("expconNG", "hospsizeNG"),
mod = nurse_lmer_3_re,
xlevels = list(expconNG = c(-0.504, 0.496),
hospsizeNG = c(-0.776, 0.224, 1.224))) %>%
data.frame() %>%
dplyr::mutate(expcon = factor(expconNG + 0.504,
labels = c("Control", "Intervention"))) %>%
dplyr::mutate(hospsize = factor(hospsizeNG + 0.776,
labels = c("Small", "Medium", "Large"))) %>%
ggplot() +
aes(x = hospsize,
y = fit,
group = expcon,
shape = expcon,
color = expcon) +
geom_errorbar(aes(ymin = fit - se, # mean plus/minus one Std Error
ymax = fit + se),
width = .4,
position = position_dodge(width = .2)) +
geom_errorbar(aes(ymin = lower, # 95% CIs
ymax = upper),
width = .2,
position = position_dodge(width = .2)) +
geom_line(aes(linetype = expcon),
position = position_dodge(width = .2)) +
geom_point(size = 4,
position = position_dodge(width = .2)) +
theme_bw() +
labs(x = "Hospital Size",
y = "Estimated Marginal Mean, Stress",
shape = "Condition",
color = "Condition",
linetype = "Condition") +
theme(legend.key.width = unit(2, "cm"),
legend.background = element_rect(color = "black"),
legend.position = c(1, 0),
legend.justification = c(1.1, -0.1))
This plot illustrates the estimated marginal means among male (gender’s reference category) nurses at the overall mean age (43.01 years), with the mean level experience (17.06 years), since thoes variables were not included as focal.predictors
in the effects::Effect()
function. Different values for thoes predictors would yield the exact sample plot, shifted as a whole either up or down.
3.9 Interpretation
There is evidence this intervention lowered stress among nurses working in small hospitals and to a smaller degree in medium sized hospitals. The intervention did not exhibit an effect in large hospitals.
3.9.1 Strength
This analysis was able to incorporated all three levels of clustering while additionally controlling for many covariates, both categorical (nurse gender and ward type) and continuous (nurse age and experience in years). Also heterogeneity was accounted for in terms of the interventions’s effect at various hospitals. This would NOT be possible via any ANOVA type anlysis.
3.9.2 Weakness
The approach presented by Hox, Moerbeek, and Van de Schoot (2017) and shown above involved mean-centering categorical variables. This would only be appropriate for a factor with more than two levels if its effect on the outcome was linear. Also, as the mean-centered variables are treated as continuous variables, post hoc tests are increasingly difficult.
3.10 Reproduction of Table 2.5
Hox, Moerbeek, and Van de Schoot (2017) presents a table on page 23 comparing various models. Note, that table includes models only fit via maximum likelihood, not REML. Also, the model \(M_3\): with cross-level interaction is slightly different for an unknown reason.
texreg::texreg(list(nurse_lmer_0_ml,
nurse_lmer_1d_ml,
nurse_lmer_2_ml,
nurse_lmer_3_ml),
custom.model.names = c("M0", "M1", "M2", "M3"),
caption = "Hox Table 2.5: Models for stress in hospitals and wards",
caption.above = TRUE,
single.row = TRUE)
| M0 | M1 | M2 | M3 | ||
|---|---|---|---|---|---|
| (Intercept) | 5.00 (0.11)*** | 5.40 (0.12)*** | 5.39 (0.11)*** | 5.39 (0.11)*** | |
| expconNG | -0.70 (0.12)*** | -0.70 (0.18)*** | -0.72 (0.11)*** | ||
| age | 0.02 (0.00)*** | 0.02 (0.00)*** | 0.02 (0.00)*** | ||
| gender | -0.45 (0.03)*** | -0.46 (0.03)*** | -0.46 (0.03)*** | ||
| experien | -0.06 (0.00)*** | -0.06 (0.00)*** | -0.06 (0.00)*** | ||
| wardtypespecial care | 0.05 (0.12) | 0.05 (0.07) | 0.05 (0.07) | ||
| hospsizeNG | 0.46 (0.12)*** | 0.46 (0.12)*** | 0.46 (0.12)*** | ||
| expconNG:hospsizeNG | 1.00 (0.16)*** | ||||
| AIC | 1950.36 | 1624.36 | 1597.48 | 1576.07 | |
| BIC | 1969.99 | 1673.44 | 1651.47 | 1634.96 | |
| Log Likelihood | -971.18 | -802.18 | -787.74 | -776.03 | |
| Num. obs. | 1000 | 1000 | 1000 | 1000 | |
| Num. groups: ward:hospital | 100 | 100 | 100 | 100 | |
| Num. groups: hospital | 25 | 25 | 25 | 25 | |
| Var: ward:hospital (Intercept) | 0.49 | 0.33 | |||
| Var: hospital (Intercept) | 0.16 | 0.10 | 0.15 | 0.15 | |
| Var: Residual | 0.30 | 0.22 | 0.22 | 0.22 | |
| Var: ward.hospital (Intercept) | 0.11 | 0.11 | |||
| Var: hospital.1 expconNG | 0.66 | 0.18 | |||
| p < 0.001, p < 0.01, p < 0.05 |
Deviance:
c(deviance(nurse_lmer_0_ml),
deviance(nurse_lmer_1d_ml),
deviance(nurse_lmer_2_ml),
deviance(nurse_lmer_3_ml)) %>%
round(1)
[1] 1942.4 1604.4 1575.5 1552.1


Benchmarking
A little script helped to benchmark this. The script is available here. It takes two arguments:
- Name of SAS program (without .sas extension)
- [Optional] Number of desired replications (default=5)
Thus, the 5 replications for the SQL with index were run using the following command:
./benchmark.ksh test-sqlset 5
The benchmark script will produce a log file called [name_of_sas_program].[number_of_replications], and print the average USER_CPU,
SYSTEM_CPU and I/O counts (sum of input and output), averaged over the number of replications, to the console. Thus, the replication
for the SQL with index produced a file called test-sqlset.5, and printed the following to the console:
Avg USER_CPU for 5 runs: 5.4208
Avg SYSTEM_CPU for 5 runs: 4.2728
Avg IO for 5 runs: 326291


iBegin Share can be setup in two different environments. It can work both with PHP (and some additional plug-ins) and without PHP. The first thing you will need to do, however, with either configuration, is to include the files in your website's header with two lines:
 <link rel="stylesheet" href="share/css/share-tool.css" type="text/css" media="screen"/>
You will need to update the javascript file with the absolute URL of your /share/
directory. Your absolute path is the base URL including the /share/ folder.
Example: share.php
is located at http://mydomain.com/mysite/share/share.php
then your absolute URL is /mysite/share/
.
// Change this to your base URL base_url: '/mysite/share/',
The plug-ins which require PHP are Email, My PC, and Print. If you do not wish to use these then you may disable them in share/script/share.js
(at the end of the file) and skip the following settings. In the share/share.php
you will find a few options for managing the emails sent via the plugin. These are actually function calls which return strings to send to the user. Basic PHP knowledge is required to edit this.
// The subject of the email to send. (Eg. "You've got new message about iBegin Share") function generateEmailSubject($title, $link, $from_name, $from_email, $to_name, $to_email) { return "{$from_name} wants you to see this link"; } // The plain text body of the email to send function generateEmailBody($title, $link, $from_name, $from_email, $to_name, $to_email, $message=null) { $output = array(); $output[] = $from_name . ' thought you might find this link interesting:'; $output[] = ''; $output[] = 'Title: ' . $title; $output[] = 'Link: ' . $link; if ($message) $output[] = 'Message: ' . messageFilter($message); $output[] = ''; $output[] = '-----------------------------'; $output[] = $from_name . ' is using iBegin Share (http://labs.ibegin.com/share/)'; $output[] = '-----------------------------'; return implode("\r\n", $output); }
There are three variables you will need to pass to attach a button. All variables are optional, but most likely you will want to at least specify link
and title
. If these are not present they default to the current page's title and url.
The third variable, content
variable should be the full URL as described in PHP's fopen()
documentation to the page which produces the trimmed content. If you pass the content
parameter it will enable two additional plugins, My Computer and Print.

The bookmarks system is flexible enough to allow you to add additional bookmarks to the list.
Due to the way iBegin Share plugins work adding a bookmark service is extremely easy. You will need to do three things to add a plugin:
share/images/icons/
. It should be formated as bm_<servicename>.gif
and 40x40px dimentions.
bm_msnlive.gif
.addService
method anywhere after share/script/share.js
has been loaded.
iBeginShare.plugins.builtin.bookmarks.addService('Digg', 'http://digg.com/submit/?url=__URL__');
__TITLE__
and __URL__
.
You are also able to choose which bookmark services appear via editing share/script/share.js
. Open up the file, and scroll to the bottom. Near the bottom you will see the list of services already added. Simply comment out any of these to disable them.
Note: To comment out a service place //
at the beginning of the line.
iBegin Share supports adding additional plug-ins beyond those that are built-in. Below is some brief documentation on creating your own plugin.
We will be using the MyPC built-in plugin for our example. The code below is slightly modified from the source to make it standalone (built-in require different method calls).
We declare our function using the standar namespacing syntax.
var iBeginSharePlugin_myPC = function() {
Inside we can add any private methods used directly by the function.
// This is a private function used by myPC to follow DRY standards. function createDocumentRow(type, label, params) { var link = escape(params.link); var title = escape(params.title); var content = escape(params.content); var tr = document.createElement('tr'); var td = document.createElement('td'); td.width = '10%'; td.paddingLeft = '50px'; var a = document.createElement('a'); a.href = iBeginShare.base_url + 'share.php?mod=send&act=mypc&f=pdf&url='+link+'&content='+content+'&title='+title; a.title = label; var img = document.createElement('img'); img.src = iBeginShare.base_url + 'share/images/icons/pc_'+type+'.gif'; img.style.border = 0; img.style.width = '40px'; img.style.height = '40px'; a.appendChild(img); td.appendChild(a); tr.appendChild(td); var td = document.createElement('td'); var a = document.createElement('a'); a.href = iBeginShare.base_url + 'share.php?mod=send&act=mypc&f='+type+'&url='+link+'&content='+content+'&title='+title; a.innerHTML = label; td.appendChild(a); tr.appendChild(td); return tr; }
Now we need to add the controls to interact with the framework. label
and render
are the only required publicly accessible methods.
return { // The label for your plug-in. Used both in the tab name and the heading. label: 'My PC', // Any parameters it requires. Link and title are always available, content may not be. requires: ['link', 'title', 'content'], // The render method passes in a callback function, which is the // iBegin Share control, and a list of parameters. render: function(callback, params) { // Create our HTML var container = document.createElement('div'); var table = document.createElement('table'); table.cellPadding = 0; table.cellSpacing = 0; table.style.border = 0; table.appendChild(createDocumentRow('pdf', 'PDF - Portable Document Format', params)); table.appendChild(createDocumentRow('word', 'Microsoft Word, Wordpad, Works', params)); container.appendChild(table); var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.name = 'shre_sbmt'; container.appendChild(iframe); // Pass the HTML as well as the params (they can be changed) // to the iBegin Share framework via a callback function. callback(container, params); } } }();
And finally we need to register our plug-in with the framework.
iBeginShare.plugins.register(iBeginSharePlugin_myPC);
Available public methods to plug-ins:
label
label = 'My Plugin'
requires
requires = ['link']
render
render = function(callback, params){}
callback(html, params)
from within render.
unload
unload = function(){}
Copyright (c) 2008 iBegin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


DemonDice Dice Roller
Pool
#5#
Tool
#5#
Diff
#5#
Torm
5
1
2
3
4
5
6
7
8
9
10
#6#
Charm
#1#
Roll
Sim
Enhance
Result area


Music Quiz
Music Quiz
Start
Check your time!
Time Left:
50
Score:
0
All Done!
Type you're initials
Submit
Top Scores
Try Again


Package org.apache.sysds.runtime.controlprogram.caching
-
Interface Summary Interface Description CacheBlock<T> Interface for all blocks handled by lazy write buffer. -
Class Summary Class Description ByteBuffer Wrapper for WriteBuffer byte array per matrix/frame in order to support matrix/frame serialization outside global lock.CacheableData<T extends CacheBlock<?>> Each object of this class is a cache envelope for some large piece of data called "cache block".CacheBlockFactory Factory to create instances of matrix/frame blocks given internal codes.CacheDataInput CacheDataOutput Custom DataOutput to serialize directly into the given byte array.CacheEvictionQueue CacheMaintenanceService CacheStatistics This singleton provides basic caching statistics in CP.FrameObject LazyWriteBuffer MatrixObject Represents a matrix in control program.PageCache TensorObject UnifiedMemoryManager Unified Memory Manager - Initial Design Motivation: The Unified Memory Manager, henceforth UMM, will act as a central manager of in-memory matrix (uncompressed and compressed), frame, and tensor blocks within SystemDS control program. -
Enum Summary Enum Description CacheableData.CacheStatus Defines all possible cache status types for a data blob.CacheStatistics.Stat LazyWriteBuffer.RPolicy MatrixObject.UpdateType


Projects List
Completed
Web Design
Landing page Design
If several languages coalesce, the grammar of the resulting language is more regular.
Pending
Android
App Design and Develop
To achieve this, it would be necessary to have uniform grammar and more common words.
Completed
Web Design
New Admin Design
To an English person, it will seem like simplified english, as a skeptical Cambridge.
Pending
Android
Custom Software Development
Their separate existence is a myth. For science, music, sport, etc uses the vocabulary
Completed
Web Design
Brand logo design
Everyone realizes why a new common language refuse to pay expensive translators.
Completed
Web Design
Website Redesign
The languages only differ in their grammar pronunciation and their most common words.


presentations attribute controls the mechanism of presentations. By default, the value is false. If the attribute value is true, a corresponding icon is added to the top right corner of the table .
presentations
false
true


Tetris By Truski
How to Play
Arrow Keys: Move Piece
Z: Rotate Counterclockwise
X: Rotate Clockwise
Shift: Store/Swap Piece
Score: 0
Game Over


Are you good financially? Are you able to make both ends meet?
Do you feel secured in the job? Do you think you need a better job?


Our Agile approach to custom IC development consistently enables our customers to get market a full 12-months ahead of traditional approaches.
At Triad, agile means fast *and flexible*. Our agile methodology means that complex design problems can be fixed in hours. New devices can be fabricated in weeks. This flexible approach to change management helps to ensure that projects stay on schedule and on budget.
We have a long heritage of developing high-performance, area-optimized mixed-signal CMOS circuits. Whether you choose our Agile ASIC, Focused Agile ASIC or Full-Custom solutions, your device will be optimized to satisfy your high-volume production cost targets.
Rapid time to market also means a supply-chain that can ramp quickly. We deliver a tightly coupled semiconductor design and production environment that accelerates prototyping, verification, qualification, production release and ramp to high-volume production.


A single modern platform for designers, digital marketers and developers.
Launch a wesbiteLorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Work on design, contents and code simultaneously since the firsts drafts – so everyone can see the iterative improvements being made instantly.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
A new way of building websites and apps that delivers better performance, higher security, lower cost of scaling, and a better developer experience
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id velit sagittis, tempor quam sit amet, congue odio.
With traditional CMSs, on page load, content is queried, generated server-side, and then sent to the user.
With a JAMstack, the heavy lifting happens during the build process itself, so the page is already generated on load. Since there isn’t any back-and-forth with a server, sites are easier to deploy, their performance improves, and many of the security risks and maintenance tasks associated with dynamic sites are eliminated.
A JAMstack also allows us to decouple our backend from our frontend, giving us more options to curate the correct architecture for clients.
© 2014-2018 Pingendo. All rights reserved


{# basic/localtoc.html ~~~~~~~~~~~~~~~~~~~ Sphinx sidebar template: local table of contents. :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #}


Below are some of our awesome products that you can buy to help support Lambda School!
Are you joining us from a cold climate? Pick up our Lambda School beanie to stay warm and represent during the few times you'll be leaving your house throughout the course.
 Buy this hoodie to stay warm and stay in class!
Price: $44.50
Our products are made from the finest materials known to man! We also offer free shipping and will give you a shout out in class if you are using nay of our products.