Prof. Francesco Vaccarino
Prof. Mauro Gasparini
Student: Roberto Franceschi (S276243)
The aim of this study is to exploit some supervised machine learning algorithms to identify the key drivers that determine the likelihood of credit card default, underlining the mathematical aspects behind the methods used. Credit card default happens when you have become severely delinquent on your credit card payments. In order to increase market share, card-issuing banks in Taiwan over-issued cash and credit cards to unqualified applicants. At the same time, most cardholders, irrespective of their repayment ability, the overused credit card for consumption and accumulated heavy credit and debts
The goal is to build an automated model for both identifying the key factors, and predicting a credit card default based on the information about the client and historical transactions. The general concepts of the supervised machine learning paradigm are later reported, together with a detailed explanation of all techniques and algorithms used to build the models. In particular, Logistic Regression, Random Forest and Support Vector Machines algorithms have been applied.
The analysis has been fully conducted with Python language, exploiting several machine learning and
statistical frameworks available such as scikit-learn
, numpy
,
pandas
, imblearn
together with other data visualization libraries
(matplotlib
and seaborn
).
Some code snippets have been reported for a better understanding of each step. Moreover, the full code implementation of this analysis is publicly available at github.com/robertofranceschi/default-credit-card-prediction.
The dataset used in this study is the Default of credit card clients from the UCI machine
learning repository, available at the following link.
It consists
of 30000 observations that represent distinct credit card clients. Each observation has
24 attributes that contain information on default payments, demographic factors, credit
data, history of payment, and bill statements of credit card clients in Taiwan from April 2005 to
September 2005.
The first group of variables contains information about the client personal information:
ID
: ID of each client, categorical variableLIMIT_BAL
: Amount of given credit in NT dollars (includes individual and
family/supplementary credit)SEX
: Gender, categorical variable (1=male, 2=female)EDUCATION
: level of education, categorical variable (1=graduate school, 2=university,
3=high school, 4=others, 5=unknown, 6=unknown)MARRIAGE
: Marital status, categorical variable (1=married, 2=single, 3=others)AGE
: Age in years, numerical variableThe following attributes contains information about the delay of the past payment referred to a specific month:
PAY_0
: Repayment status in September 2005 (-1=pay duly, 1=payment delay for one month,
2=payment delay for two months, … 8=payment delay for eight months, 9=payment delay for nine months and
above)PAY_2
: Repayment status in August 2005 (same scale as before)PAY_3
: Repayment status in July 2005 (same scale as before)PAY_4
: Repayment status in June 2005 (same scale as before)PAY_5
: Repayment status in May 2005 (same scale as before)PAY_6
: Repayment status in April 2005 (same scale as before)Other variables instead consider the information related to the amount of bill statement (i.e. a monthly report that credit card companies issue to credit card holders in a specific month):
BILL_AMT1
: Amount of bill statement in September, 2005 (NT dollar)BILL_AMT2
: Amount of bill statement in August, 2005 (NT dollar)BILL_AMT3
: Amount of bill statement in July, 2005 (NT dollar)BILL_AMT4
: Amount of bill statement in June, 2005 (NT dollar)BILL_AMT5
: Amount of bill statement in May, 2005 (NT dollar)BILL_AMT6
: Amount of bill statement in April, 2005 (NT dollar)The following variables instead consider the amount of previous payment in a specific month:
PAY_AMT1
: Amount of previous payment in September, 2005 (NT dollar)PAY_AMT2
: Amount of previous payment in August, 2005 (NT dollar)PAY_AMT3
: Amount of previous payment in July, 2005 (NT dollar)PAY_AMT4
: Amount of previous payment in June, 2005 (NT dollar)PAY_AMT5
: Amount of previous payment in May, 2005 (NT dollar)PAY_AMT6
: Amount of previous payment in April, 2005 (NT dollar)The last variable is the one to be predicted:
default.payment.next.month
: indicate whether the credit card holders are defaulters or
non-defaulters (1=yes, 0=no)The main aim of the data is to discriminate clients that are predicted to credit card default the next
month, according to the default.payment.next.month
column which is set to “0” for
non-defaulters and “1” for defaulters. Thus, it is a binary classification
problem on a relatively unbalanced dataset, as shown in the following figure.
As can be seen, a number of 6.636 out of 30.000 (or 22.1%) of clients will default next month (i.e. has category 1).
In order to get a first look at how data is presented, some observations of the dataset are shown in the
table below. No missing feature is recorded for any of the 30.000 samples.
The data appears already
encoded and cleaned. However, by better inspecting some samples and statistics there are some observations
to point out.
# read input
data = pd.read_csv(INPUT_PATH)
data.head()
# Summary Statistics
data.describe()
For example, the following criticities can be observed:
PAY_0
should be renamed in PAY_1
;EDUCATION
and MARRIAGE
there are some undocumented categories;PAY_n
have a minimum in -2 (undocumented) and a maximum equal to 8. So, it is
likely that these attributes need to be rescaled.
### 2.2 Data CleaningThe presence of errors in the dataset can be addressed in two ways:
In our case the first method is applied to the categorical variables, by safely removing the unknown categories (since the total number of anomalous values is 399):
EDUCATION
there are three categories not listed in the
description of the dataset provided by the UCI website that corresponds to 0, 5, and 6.MARRIAGE
we can notice the presence of category 0 that does not correspond to
any categories previously described.while the second approach is applied to the PAY_n
features, correcting them by adding 1 to
each attribute and grouping together the resulting values of '0' and '-1' (since 0 does not correspond to
any value previously described).
data['MARRIAGE'].value_counts()
data['EDUCATION'].value_counts()
# Payment delay description
data[['PAY_1', 'PAY_2', 'PAY_3', 'PAY_4', 'PAY_5', 'PAY_6']].describe()
# Amount of given credit in NT dollars
data.LIMIT_BAL.describe()
The very high value of standard deviation has been further investigated. As can be seen, most of defaults are for credit limits 0-100,000 (and density for this interval is larger for defaults than for non-defaults).
Another relevant point which could affect the classification performances is the correlation among features: the presence of strongly correlated features may lead to a decline in the performances of some classification algorithms which assume that the predictors are all independent. Another benefit from spotting correlation among features is that the same information may be encoded with less attributes, and this could lead to simpler final models. Indeed, some methods suffer from high dimensional datasets (especially distance based ones with a few number of samples), so reducing the dimensions of the feature vectors can make it more trustable and stable, if the discarded dimensions don’t affect significantly the total original information.
The Pearson’s correlation coefficient is a statistic that measures the linear correlation between a pair of random variables. In particular, given the random vector $(X, Y)$ :
\begin{equation} \rho _{X,Y} :=\ \frac{Cov( X,\ Y)}{\sigma _{X} \sigma _{Y}} \ \in \ [ -1,\ 1] \ \subset \ \mathbb{R} \end{equation}where $Cov( X,\ Y)$ is the Covariance and $\sigma _{X}$ and $\sigma _{Y}$ are respectively
the standard deviations of $X$ and $Y$.
$\rho _{X,Y}$ has a value bounded between $-1$ and $+1$.
The higher in absolute value, the stronger the linear correlation is, meaning that on average for each
large (w.r.t. the mean of $X$) value observed, a corresponding large value (w.r.t. the mean of $Y$) is
also observed.
Note that the Pearson’s correlation coefficient is only able to capture linear trends, hence it might lead
to a value of 0 for strongly non-linearly correlated variables (e.g. quadratic trend).
In practice, the underlying probability distribution is not known but only a sample of realizations of the random vector $(X,Y)$ is available, so the statistic is computed by inferring the unbiased covariance and standard deviations from the dataset for each pair of attributes.
The following representation shows the correlation matrix between features: since the way the matrix is
constructed make it
symmetric and because of the large number of features, only the heatmap of the lower diagonal matrix (the
diagonal itself is
excluded) is reported for a better visualization.
As shown in the correlation matrix above, some features show high correlations with each other. In
particular, there exist an high positive correlation among the BILL_AMTn
features, for
example:
To give a further proof of the linear dipendence of these variables, their interactions plots are shown:
The charts confirm what expected, the features in the same graph shows a linear trend as the Pearson
coefficient suggested,
indicating they encode pretty similar information.
For completness, also charts of non-strongly correlated features are reported. In particular:
The following charts are obtained:
In order to remove correlated features some dimensionality reduction techinques will be performed during the Data Preprocessing phase (point 3.).
Finally, the boxplots for each of the numerical features are plotted in order to have a further look at the distribution and to eventually spot outliers (i.e. “bad” data that either has been misscomputed or is strongly exceptional wrt expected range).
A boxplot represents a 5-number summary of data:
In the boxplot we define the Interquartile range (IQR) as is the distance between the upper and lower quartiles:
\begin{equation} IQR = Q_3 - Q_1 = q(0.75) - q(0.25) \end{equation}
Furthermore, through them it is possible to detect outliers presence (i.e. the single points), with the following rule: every sample located beyond $Q_1 − 1.5IQR$ and $Q_3 + 1.5IQR$ is considered an outlier.
Note that for the purpose a normalization was applied on all the attributes to make them have the same scale, otherwise it would have been impossible to compare them.
As mentioned above, input variables may have different units so different scales and magnitude; for this
reason before drawing a boxplot, a MinMaxScaler()
is applied in order to scale the features
between a range $(0, 1)$. The basic idea behind this rescaling technique is that for every feature, the
minimum value of that feature gets transformed into a 0, the maximum value gets transformed into a 1, and
every other value gets transformed into a decimal between 0 and 1.
The transformation is given by the following formula:
where $X_{min}$ is the minimum value on the column and $X_{max}$ is the maximum value on the column.
This transformation is applied on numerical features only as the categorical variables has been already transformed into one-hot vectors, that rescale the categorical variable in the range $(0,1)$.
Another possible scaling technique is referred to as standardization, which transforms each value $x$ of feature $X$ as follows, independently for each column:
\begin{equation} x^{\prime} =\frac{x-\mu _{X}}{\sigma _{X}} \end{equation}where $\mu_{X}$ is the sample mean and $\sigma_{X}$ is the sample standard deviation of feature $X$. This way, we force each attribute to have a new empirical mean value of 0 and variance 1.
Note that the statistics (e.g. mean and standard deviation for the Standard Scaler) are computed on the training set only, then both datasets are transformed according to them. This is very important as it avoids leaking any information from the test set into the training process, which would be formally incorrect by definition.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler() # or MinMaxScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
According to the method previously described from the boxplot we can identifty many outliers. It is possible to change the multiplicative coefficient to decide the threshold to overcome to be considered an outlier, but still dubling the coefficent the possible outliers are more than 4000.
Due to the lack of domain knowledge and the very high number of identifiable outliers, no samples have been discarded as outliers.
An integer encoding may impose some ordinal relationship between categorical variables that does not
exist, for this reason a one-hot encoding is performed.
Categorical variable such as SEX
, MARRIAGE
and EDUCATION
are turned
into one-hot variables in order to remove any orders that in this case have no meaning.
A one hot encoding is a representation of categorical variables as binary vectors. This first requires that the categorical values be mapped to integer values. Then, each integer value is represented as a binary vector that is all zero values except the index of the integer, which is marked with a 1.
As previously explained, a MinMaxScaler()
has been already applied to all the numerical
features.
The dataset is divided in training set and test set, with the proportion 3:1. Given the imbalance fashion
in the original dataset a stratified sampling strategy has been applied during the split,
so that the proportion of defaulters/non-defaulters clients in each of the final sets would approximately
be the same as the initial one. This approach is desirable every time we have a highly unbalanced
dataset as in our case.
The final split obtained is distributed as follows:
| Non-defaulter | Defaulters | Total | |
|---|---|---|---|
| Training set | 17246 | 4954 | 22200 |
| Test set | 5750 | 1651 | 7401 |
Note that the split has been randomly generated but kept the same for all the following methods, in order to to make evaluations statistically meaningful and provide consistent results among the different classifiers.
random_state = 42
As already mentioned above, generally speaking it is not always a good idea to propagate in the algorithms all the samples attributes. The main reasons for which the dimensionality reduction is required are the following:
Moreover different machine learning algorithms suffer from the curse of dimensionality problem: if there are more features than observations the risk of massively overfitting the model becomes really high. Then, too many dimensions causes every observation in the dataset to appear equidistant from all the other, and this is effectively a problem when used distance-based algorithms (such as K-Nearest Neighbors), because if the distances are all approximately equal, then the observation appear equally alike (as well as equally different), making the algorithm perfomances meaningless.
On top of feature transformation, the data preprocessing phase often includes a feature selection step,
where the attributes from the given dataset are carefully analysed and finally selected to feed the
machine learning algorithm.
As previously seen in the correlation matrix and with scatter plots, some
features in this dataset are strongly linearly correlated, so it is not meaningful to keep all of them
because they may contain redundant information. In this way, features with a Pearson coefficient $\rho
\geqslant 0.92$ with other predictors should be discarded.
In particular, at this step the following features are removed: BILL_AMT2
,
BILL_AMT3
, BILL_AMT4
, BILL_AMT5
, BILL_AMT6
.
Another way to obtain a lower dimensional dataset when dealing with multicollinearity among features, is by applying a Principal Component Analysis (PCA) to our data.
PCA is an unsupervised learning technique that performs a linear transformation (by means of an orthogonal matrix) of the original space such that the new basis found has dimensions (features) that are sorted from largest to smallest possible variance.
The intuition behind this approach lies in the fact that if the direction of maximum variance in the
original space is not directly captured by the features in the dataset, then that direction might be used
to construct a new feature that has a larger variance, hence probably encoding more information.
Therefore, the following minimization problem is performed for finding the direction of maximum variance:
\begin{equation}
X\in \mathbb{R}^{n\times d} ,\ dataset\ centered\ in\ zero\\
\Sigma :=\frac{X^{T} X}{n-1} \ ,\ sample\ covariance\ matrix\\
\\
find\ \ \overrightarrow{z_{1}} :=a_{1}\overrightarrow{e_{1}} \ +...+a_{d}\overrightarrow{e_{d}}\\
\\ \\
\\ s.t.:\\
\\
\ \overrightarrow{z_{1}} =\underset{\vec{z}_{1}}{argmax} \ \ \vec{z}_{1}^{T} \ \Sigma \ \ \vec{z}_{1} ,\
subject\ to:\ \Vert \vec{z}_{1}\Vert =1
\end{equation}
Recursively, all other dimensions are then computed in the same way, additionally forcing them to be
orthogonal to the previous dimensions found.
The new features $z_{i}$ are denoted as principal components (PCs).
The above optimization problem can be easily proved to be equivalent to computing the eigendecomposition
of $\Sigma$ and selecting its eigenvectors as the principal components, which is the way PCA is executed
in practice.
In particular, being $\Sigma$ a symmetric positive semidefinite matrix, it is always possible to
diagonalize it by means of an orthogonal matrix $P$ (eigenvectors of $\Sigma$), and obtain the resulting
similar matrix $\Lambda$:
\begin{equation}
\Lambda =\ \begin{pmatrix}
\lambda _{1} & 0 & \cdots & 0\\
0 & \lambda _{2} & \ddots & \vdots \\
\vdots & \ddots & \ddots & 0\\
0 & \cdots & 0 & \lambda _{d}
\end{pmatrix} \ ,\ \lambda _{1} \geqslant \lambda _{2} \geqslant ...\geqslant \lambda _{d} \geqslant 0,\
\lambda _{i} \in \mathbb{R}^{+}
\end{equation}
where $\lambda_{i}$ are the eigenvalues of $\Sigma$, or equivalently the variances of the new features found. Note that, since $\Sigma$ and $\Lambda$ are similar matrices, they have the same trace, meaning that the initial total variance among the features of the dataset is not changing but it is just getting redistributed on new axes (which is expected as all we are doing is just a rigid rotation of the space). In particular, we can compute the variance explained by each new principal component with respect to the total variance of the dataset.
Finally note that $\Lambda$ is now the covariance matrix in the new basis found, and since it’s diagonal all new features result to be linearly uncorrelated. We can then select only a subset of the first principal components in order to reduce the initial dimensionality of the dataset with minimal information loss.
In this study, PCA has been performed on the Credit card dataset to deal with the multicollinearity problem and reduce the number of dimensions. The following figure shows how the variance has been redistributed on the new features extracted.
The graph above is relative to the proportion of explained variance: the green line represents the proportion of variance explained by each principal component. While the orange line instead is the cumulative amount of proportion of variance explained (i.e. the sum of the single variance explained by the principal components on the left).
This graph is useful to take decision regarding the number of components to keep. The following table report some interesting values for the number of components.
| Number of PCs | Cumulative Variance Explained |
|---|---|
| 4 | 89.7 % |
| 6 | 95.3 % |
| 8 | 97.1 % |
| 12 | 99.0 % |
For example, the first 12 PCs are able to capture almost the whole variance (99%) of the data points and for this reason we decided to keep these number.
Note that PCA is applied based only on the training data in order to avoid any leaking the information of test data.
from sklearn.decomposition import PCA
pca = PCA(n_components=len(X_train.columns))
pca.fit(X_train) # not on the whole dataset
When dealing with significantly unbalanced dataset in the target label, it becomes harder for most machine learning algorithms to efficiently learn all classes. The training process might be indeed biased towards a certain class if the dataset distribution is poorly balanced.
In the specific case of the credit card clients, only about 22.1% of the data are labelled as defaulters (y=1).
| Number of rows | Percentage | |
|---|---|---|
| Non-defaulters (class=0) | 17246 | 77.68 % |
| Defaulters (class=1) | 4954 | 22.32 % |
While the obvious and most desirable solution would be to collect more real data, oversampling
and undersampling are techniques that may still come in handy in these situations.
For both
techniques there is a naïve approach that is the random oversampling
(undersampling) where training data is incremented (decremented) with multiple copies of
the samples, until the same proportion is obtained on the minority (majority) classes.
With SMOTE algorithm the minority class is augmented artificially, by constructing new synthetic samples
randomly positioned in between one point and its k-neighbors. In other words, given a limited set of data
points that belong to the class that we wish to augment, we trace high-dimensional lines connecting the
data points and we draw new samples from such lines.
from imblearn.over_sampling import SMOTE
clf = Classifier(**best_config)
#oversampling
oversample = SMOTE()
X_train, y_train = oversample.fit_resample(X_train, y_train)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
Cluster Centroids make use of K-means algorithm to perform undersampling. After finding the cluster
centroids on the majority class points, the algorithm selects the instances belonging to the cluster
(labelled with the majority class), which are furthest from the cluster centroid in feature space. These
data points are considered as the most unimportant instance. On the contrary, the instance belonging to
the majority class, that is nearest to the cluster centroid in feature space, is considered to be the most
important instance. In this way, instances belonging to the majority class are removed on the basis of
their importance.
In this section a description of the algorithms used is presented. For each of them, different hyperparameters are tuned in order to find the ones performing best, and, once found, the model trained with them is used to predict class labels on test data combining different data preprocessing techniques presented above in order to spot some performances differences. The best configuration is selected by comparing the different metrics, principally based on the f1-score because accuracy on unbalance dataset may return an high values even if the minority class is not correcly classified.
Each algorithm is trained on dataset with different preprocessing techniques combined:
The algorithms considered are:
Even though learning theory guarantees to reach a probably approximately correct model, in practice it is often needed to perform an actual evaluation of the model to check how well it’s performing. This is generally done by splitting the original dataset into two parts, a training set and a test set, so that the model can be trained on the training samples only and be evaluated on previously unseen data. This technique is referred to as Holdout.
Instead, Cross Validation is a resampling method that allows to repeteadly drawing samples from a training set, in order to have a better estimate of the evaluation score used.
With the cross-validation technique we aim to verify if the model is able predict labels for data points that it hasn't seen so far. The complete dataset is divided into $k$ subsets (folds):
This operation is repeated $k$ times in order to reduce the variability, and at every round the validation subset changes. At the end, the $k$ estimates of model's predictive performance are averaged.
The special case where $k$ is equal to $m$, the number of examples, is called leave-one-out. This procedures gives a very good estimate of the true error but, on the other side, it computationally expensive.
A combination of holdout and cross validation can be used when also dealing with a validation set
(k-fold cross validation). So after having divided the dataset in a stratify way into training
and test set, the training part is again splitted into training and validation set (even in this case with
stratify=True
).
In a classification scenario, the model can be evaluated by computing different metrics. In order to better understand these metrics could be useful to get some fundamentals:
| Predicted (1) | Predicted (0) | |
|---|---|---|
| Actual (1) | TP | FN |
| Actual (0) | FP | TN |
Some of the most popular metrics are:
The last three measures are class-specific, and a global metric can be obtained by averaging their values across all classes in the dataset. In particular, class-specific metrics work better when dealing with significantly unbalanced dataset, as they take into account how every class is being learned. Thus, the accuracy measure alone might be misleading in these cases, since may return an high score even if the minority class is not correcly classified.
In this analysis we focus our attention in detecting which customer may be defaults clients, and the positive class captures the attention of the classifier. As already mentioned, in the following section the F1-score will be used to find the best configuration for each model.
Logistic Regression is a parametric, discriminative binary classification algorithm. The name is given after the fact that the algorithm can be interpreted as part of the Generalized Linear Model, where the response variables are distributed according to a Bernoulli distribution. In particular, the model assumes the predictors to be linked to the mean of the response variables ($p_i$) as:
\begin{array}{l} observation\ ( x_{i} ,\ y_{i}) \ ,\ y_{i} \ realization\ of\ Y_{i} \sim Bernoulli( p_{i}( x_{i}))\\ \\ log\left(\frac{p_{i}}{1-p_{i}}\right) =w^{T} x_{i} \ \Longleftrightarrow \ p_{i} =\frac{1}{1+e^{-w^{T} x_{i}}} \ ,\ for\ all\ i \end{array}
The algorithm then tries to find the MLE (or MAP if a regularization term is added) of the mean of the response variables, by acting on w, assuming i.i.d. samples. Unlike ordinary least squares the optimization problem does not have a closed-form solution, but it’s instead solved through numerical methods.
\begin{equation} \underset{w}{\max} \ \prod ^{n}_{i} \ p_{i}( x_{i} |w)^{y_{i}} \ *\ ( 1-p_{i}( x_{i} |w))^{1-y_{i}} \end{equation}A regularization term is often added to prevent the coefficients to reach a too high value which would overfit the training data. The hyperparameter $C$ is applied on the new penalty term and inversely regulates the strength of the regularization in the objective function.
# C values tuned:
params = {'C': [0.0001, 0.001, 0.01, 0.1, 1, 10]}
#Best configuration found C=0.01
#Data prepocessing: PCA+SMOTE
As a result, the output of a Logistic Regression model is the probability of the input sample to be of class 1, hence a confidence measure is also returned when predictions are performed. On top of this, the coefficients returned by the algorithm may give interpretable insights on what attributes are contributing the most to a higher output value, and viceversa.
Decision Trees are the most intuitive and interpretable machine learning model, which predict the target label by learning simple decision rules inferred from the data. At each step, a Decision Tree picks the feature that best divides the space into different labels, by means of the GINI impurity measure: \begin{equation} GINI( t) =\sum ^{C}_{i=1} p_{t}( k)( 1-p_{t}( k)) =1-\sum ^{C}_{i=1} p_{t}( k)^{2} \end{equation}
where $p_{t}(k)$ is the frequency of class $k$ appearing in node $t$, with $C$ the total number of classes. The lower the measure, the less impure the node is.
A full tree is then constructed on the training data and will be used at classification time for predicting the target label. To avoid overfitting, decision trees are often pruned before reaching their full growth, and predictions are made according to a majority vote on the samples of each leaf. Note how trees cannot handle missing values, but do not suffer from feature scaling since each attribute is treated independently.
#Best configuration found
#Data prepocessing: PCA+SMOTE
params = {'criterion': 'gini', 'max_depth': 5, 'min_impurity_decrease': 0.0, 'splitter': 'best'}
Decision trees are however considered weak learners in the majority of cases, and ensemble techniques such as bagging (Random forests) or boosting are in practice applied on them in order to reach higher performances and more robust solutions.
Random Forests are an ensemble method made out of multiple decision trees. Each individual tree in the random forest makes a class prediction and the class with the most votes is assigned to the sample taken into account. The intuition behind this method is that a large number of relatively uncorrelated models (trees) operating as a committee will outperform any of the individual constituent model. Uncorrelation is the key of Random Forests better performances, and this is ensured through two methods:
Overall, each tree is then trained on a sample of the same size of the training dataset chosen with replacement, and their splits consider different features every time. This way, even if the decision trees are somewhat overfitting the data and are unstable (high variance), the overall prediction will be statistically more robust (low variance) because of Central Limit Theorem. The prediction is indeed performed by picking the majority class out of all trees.
Hyperparameter tuning on the number of trees and pruning factors for each tree has been performed. In particular, note how the predictions tend to be better as the ensemble model grows horizontally in size:
#Best configuration found
#Data prepocessing: PCA+SMOTE
params = {'criterion': 'gini', 'max_features': 'sqrt', 'n_estimators': 100}
Finally, tree-based model can be used as an alternative method for feature selection. They indeed provide
a feature importance measure by means of how much the gini index has been affected on the various splits
with the given feature.
Compared to decision tree, random forests guarantee robustness and are less prone to overfitting.
Support vector machine (SVM) is a parametric linear classification algorithm that aims at separating two classes through a hyperplane in the data dimension. Once the hyperplane has been set, the prediction rule is simply based on whether the test point lays on one side or the other one of it. If more than one hyperplanes fit the purpose, the one with the higher margin (distance) between support vector (the closest points to the hyperplane, one for each class) is selected.
More formally, it tries to define $\vec{w} ,b$ s.t. the hyperplane $w^{T}x+b=0$ is the “maximum-margin hyperplane”, i.e. such that the distance between the hyperplane and the nearest point $\overrightarrow{x_{i}}$ from either group is maximized. If we impose that all points must satisfy $|w^{T}x+b|\geqslant1$ relatively to their label, then the optimization problem becomes equivalent to finding the smallest $\|w\|$:
The decision function on a binary label $\{-1,1\}$ is then applied as $x\mapsto sign\left( w^{T} x+b\right)$. Geometrically, the best hyperplane is completely determined by the points $\overrightarrow{x_{i}}$ the lie right on the boundary of the margin $|w^{T}x_{i}+b|=1$. These points are called support vectors.
This model is referred to as Hard-margin SVM, where data is supposed to be linearly
separable otherwise no feasible solution would be found.
To extend the model on classes that are not perfectly separable by means of a hyperplane, the hinge loss function is introduced, which penalizes points falling in between the margin:
\begin{equation} L_{hinge} :=\ \max\left( 0,\ 1-y_{i}\left( w^{T} x_{i} +b\right)\right) \end{equation}The previous opt. problem is then generalized to the soft margin version, which is equivalent to the ERM of the hinge loss with an L2 regularization term:
Where $𝝃$ is a slack variable which is introduced to in order to soften the misclassification
constraint, allowing the model to make a certain number of mistakes and letting the margin to remain as
wide as possible. The amount of misclassification allowed
is controlled by the hyperparameter $C$ which regulates the strength of the Hinge Loss term introduced: a
higher $C$ will lead to a stronger minimization of the hinge loss (forcing training points to be more
correctly classified), while a lower $C$ imposes a harder regularization (allowing more
missclassifications) leading to a larger margin.
The Lagrangian Dual Problem is as follows:
By solving the dual problem (e.g. through Quadratic Programming) we find $w=\sum ^{n}_{i} \alpha _{i} y_{i} x_{i}$ as a linear combination of the training data. In particular, only the points that are in between (or exactly on) the margin will have an $\alpha_{i}\neq0$, i.e. only the so called support vectors are affecting the decision function. The latter can be then also denoted as $sign\left(\sum \alpha _{i} y_{i}\left( x^{T}_{i} x\right)+b\right)$, with $x$ a generic test observation. Note how in the soft margin version the number of support vectors found is generally much higher.
Starting from these assumptions, the SVM model can still be generalized to multiclass classification and non-linearly separable classes. Indeed, as for the second case, classes may follow a non-linear pattern and a hyperplane may not be the best fit for explaining our data. However, the given dataset might still be linearly separable when mapped onto a higher-dimensional space by a non-linear function phi, such that the training points are transformed as $x_{i} \in \mathbb{R}^{d} \mapsto \varphi ( x_{i}) \in \mathbb{R}^{d^{\prime}} ,\ d^{\prime}>d$.
While it is possible to compute the SVM algorithm in a higher space through a feature map , a different trick is instead practically used which exploits a nice property of support vector machines. Indeed, note that the dual optimization problem and the newly defined decision function solely depend on the scalar product between the various points at all times. Whenever this property is satisfied then no matter what the higher-dimensional space is, all that it’s needed is a function able to compute the scalar products in the new transformed space.
We then introduce the notion of a Kernel function $K$, defined as:
\begin{equation} K\ :\ \mathcal{X} \times \mathcal{X}\rightarrow \ \mathbb{R}\\ ( x,\ x^{\prime} ) \mapsto K( x,\ x^{\prime} ):=\varphi ( x)^{T} \varphi ( x^{\prime} ) \end{equation}for some feature map $\phi$. Then, we can simply apply different kernel functions to the SVM algorithm without directly knowing what the feature transformation is. The power of this approach relies on the Mercer’s Theorem, which gives a sufficient condition for a function to be a Kernel for some unknown $\varphi ( \cdot )$.
Common Kernel functions used are:
In particular, for the Gaussian RBF Kernel the decision function is computing a similarity measure between the test point and the support vectors (weigthed by $\alpha_i$ and $y_i$), which is inversely proportional to the euclidean distance of the two, leading to an output label that is determined by the closest support vectors. The new hyperparameter gamma can be tuned to regulate how many support vectors are significantly affecting the decision function (usually set to auto: $1/n\_features$ or scale: $1/( n\_features*X.var())$).
The decision function then becomes $sign\left(\sum \alpha _{i} y_{i}K(x_{i},x)+b\right)$. Finally note that while it is convenient to use a kernel function, the hyperplane in higher space is not actually computed when the kernel trick is applied, i.e. the coefficients of the vector $w$ are unknown.
In this study, both the linear and the gaussian RBF Kernels have been tried. The regularization parameter $C$ has been hypertuned to find the best performing model on the training set.
#Best configuration found
#polynomial kernel
params = {'C': 100, 'kernel': 'poly'}
#kernel RBF
params = {'C': 1, 'kernel': 'rbf'}
| Precision | Recall | F1-score | |
|---|---|---|---|
| SVM POLY + SMOTE | 0.64 | 0.28 | 0.39 |
| SVM RBF + SMOTE | 0.54 | 0.52 | 0.53 |
| SVM POLY + Cluster Centroids | 0.68 | 0.18 | 0.29 |
| SVM RBF + Cluster Centroids | 0.58 | 0.46 | 0.51 |
In conclusion, also in this case the results should be improved in order to get more accurate prediction on default credit card clients.
The following barplot displays a summary of results in the training-validation phase where all the algorithms are trained with their best hyperparameters (i.e. the ones that maximize the f1-score on positive class), with both techniques presented to overcome class imbalancing problem.
All the above mentioned algorithms have been tried out and hypertuned on the training set with a k-fold cross validation technique. The best performing configurations of hyperparameters on the training set have been chosen for rebuilding the models and evaluating them on the test set.
Finally, the table below the results of all the classifiers (in their best configuration) on validation and test set are compared. As we can see, the score obtained on the test set are pretty close to the one obtained in validation.
| Algorithm | Validation Accuracy | Validation F1-score | Test Accuracy | Test F1-score |
|---|---|---|---|---|
| LR + PCA | 0.75 | 0.45 | 0.73 | 0.44 |
| LR + PCA + OS | 0.81 | 0.50 | 0.81 | 0.51 |
| LR + PCA + US | 0.77 | 0.51 | 0.77 | 0.51 |
| RF + PCA | 0.74 | 0.47 | 0.74 | 0.47 |
| RF + PCA + OS | 0.75 | 0.46 | 0.75 | 0.48 |
| RF + PCA + US | 0.47 | 0.38 | 0.47 | 0.37 |
| SVM + PCA | 0.77 | 0.52 | 0.78 | 0.52 |
| SVM + PCA + OS | 0.78 | 0.53 | 0.78 | 0.52 |
| SVM + PCA + US | 0.77 | 0.51 | 0.77 | 0.51 |
Note that the scores obtained on the test should never be treated as additional information to change how the training is performed, but only as a final evaluation of the model.
In this study different supervised learning algorithms have been inspected and presented with their mathematical details, and finally used on the UCI dataset to build a classification model that is able to predict if a credit card clients will default in the next month. Data preprocessing makes algorithms perform slightly better than when trained with original data: in particular, PCA results are approximately the same, but the computational cost has been lowered. Oversampling and undersampling techniques has been combined with PCA to assess the dataset imbalance problem. Oversampling as mentioned performed slightly better w.r.t. the undersampling, this is likely because the model is trained on a large amount of data. However, all the models implemented achieved comparable results in terms of accuracy.
[1] Default of credit card clients Data Set: UCI dataset link
[2] Yeh, I. C., & Lien, C. H. (2009). The comparisons of data mining techniques for the predictive
accuracy of probability of default of credit card clients. Expert Systems with Applications, 36(2),
2473-2480. link
[3] Understanding Machine Learning: From Theory to Algorithms, S. Shalev-Shwartz, S. Ben-David, 2014 link


Use The Most Efficient Models
Intent
Motivation
Applicability
Description
Large models are often associated with richer representations and better performance. However, the use of large models can lead to inefficient resource management. For example, using smaller models which run at smaller resolutions for image classification tasks can potentially improve computational, storage and data transfer resources – ultimately leading to smaller development and operational costs and to a smaller carbon footprint. Therefore, the use of efficient models should be a critical objective in any application of ML.
There are multiple ways to select or develop efficient models, either by (i) selecting a model with less parameters for the task at hand or (ii) compressing, pruning or distilling a large model to a more efficient one. The first strategy should always be tested, as thin models can bring more advantages – e.g., better interpretability or faster run times. For example, if a Random Forest model performs on par with a neural network, the former should always be employed.
The second strategy should be tested when only large models can be employed for the task at hand. Compression, pruning or distillation are known strategy that can be used to significantly reduce the number of parameters of a model while also maintaining performance.


[continued]
The sand near my area is very excellent. But one ought to, in order to make an excellent work, take it fresh, which has been used. For it dries out after having been cast several times & is lean & has no body. If you cast some fine work which has hardly any thickness, it is necessary that your substance of copper be very hot so that it penetrates & runs. Some mix in lead with the fonte, but it is for large works & not for small ones.
Sand from oxen feet twice—burned & finely ground melts more neatly with copper, & without a chappe, than sand that I have seen. I have cast a high relief medal with it, and f with a thickness as delicate as a knife blade or a card. And being a hollow on one side, opposite the relief which was on other side.
It is necessary that lean sands be more moistened than others, namely with magistra or good pure wine or boiled wine with elm tree roots & similar things. But very fine sands, like burned linen which is fat & soft on its own, want to be applied dry.
All moistened sand wants to be very beaten & stirred in order to be ground finely \& to flatten out the clods that it makes in itself when it is bathed.
The olive oil that some mix in with beaten egg white makes it become porous.
Sand of calcined glass withstands several castings, but there are only the first ones. It also becomes porous.
Latten comes out on its own, but it runs too quickly. It is good to mix it with a little copper, like a quarter part, with the substance of skillets.
Founders cast well box molds up to 30 or 40 lb, but not more.
at left middle margin
It is good for big work, but for small ones it is troublesome for releasing. Il because it crumbles. It is good that it be alloyed with some fat thing which has bond such as molded tripoly or ab burned felt or sal ammoniac or tripoly & similar things.
[continued]


${Favorite tags let you follow or participate in questions you care about}
${Type your favorite tags here}


-
Basic
Basic Calls can be added by simply entering the text to be displayed on the card without any fancy syntax stuff. These Calls are automatically limited to one response per player. Calls without any underscores will not appear multiple times after completing a round.
Examples:
What made Spock cry?
-
Underscores
Instead of appending a player's response to the end of the call's text, underscores can be placed anywhere to complete a call's text. With more underscores, the players need to pick more cards to participate in a round.
Examples:
You know, once you get past _, _ ain't so bad.
-
Composite Formatting
Composite Formatting allows you to reuse text in multiple places. The syntax is similar to C#, and can be used as many time as you want. This can also be combined with underscores, though you cannot reuse the text from those.
Examples:
He said that {0} on {1} is better than {0} without {1}
-
HTML Support
Calls also support HTML, so you can include images, colored text or even unnecessary animations.
Examples:
This is a call that renders _ in uppercase


House pressing higher vape taxes
by Jester P. ManalastasWITH or without the ban, the House of Representatives is pushing for higher taxes on vape products.
Albay Rep. Joey Salceda, chairman of the House Committee on Ways and Means, said that the House will retain the taxes on vape and might even go for higher rates.
“We will retain it pero given the logic of the initial reported ban -- precautionary principle -- we might go for a higher rate than P25/ml to P45/ml,” Salceda said.
According to the House leader, the 1 million vape users are almost totally upper middle to high income class versus the 23 million smokers with 7 million in the lowest 50 percent.
The entry cost to vaping is relatively high at P1,600.
President Rodrigo Duterte has ordered a ban on vaping in public and even ordered the arrest of violators.
Salceda said they will repeal Section 2 of Republic Act 11346, passed by the previous (17th) Congress, even if it means revenue loss of P1.4 billion for the government.
In August, the House of Representatives approved on third and final reading HB 1026 raising anew the excise taxes on alcohol products, heated tobacco and vapor products.


28 May 2016
Can you think of an analogy to describe HTML, CSS and the DOM? How would you describe it to a non technical friend?
The house analogy is a well knonwn and simple way to explain the relationship between HTML, CSS and DOM (document object model). Say you are building a house, the HTML is the house footprint. Each has house an entrance, kitchen, hallway and bedrooms. Similarly each HTML footprint has a head, title, body and divs (sections within the body). CSS is used to design, style and position the house components. CSS will determine how the big the rooms are, their position in relation to each other, what colour the walls will be etc. You can apply CSS style rules to one room, or the whole house. The DOM is the platform used to present and interact with objects in HTLM. Keeping with the house analogy - the DOM would be the platform that is used by an architecture firm to present (and interact with) the house components, layout and design.
What is meant by boxifying design?
Everything you see on a webpage is in a box. As a web designer this is helpful because when you think of a web page as being made up of a series of boxes - you literally can break down a page into boxes and focus on designing one element (one box) at a time. It gets complicated though, because you can have boxes within boxes within boxes!
What is the box model?
The box model considers each and every HTML element within a box. Each box consists of margins, borders, padding and the actual content. The relationship between the four elements of a box (margin, border, padding and content) can be defined using CSS (the styling language referred to above).
What is grid based design?
Grid based design takes advantage of the way people read (left - right, then top - bottom). It uses columns and rows within the page to make the most out of a page layout. A grid is split in rows and columns and HTML elements are placed in each area of the grid. Typically a row is divided into 12 columns (1/12 = 8.33% of the page, 2/12 = 16.66% of the page and so on). 12 is a good number to use because the width of a page is typically divided into 2,3 or 4 sections and 12 is the lowest number that is divisible by all those numbers.
Whats all the hype about responsive webpages?
People use many different devices to access the internet. Each device is different - not just in the size of screen but also in terms of the resolution of the screen (pixels). Responsive design means your website looks great for everyone regardless of the device they are using. When implementing responsive design the main thing you need to remember is not to use fixed pixel size to determine width - but instead state the pixel as a percentage of the screen. For example, 300 pixels = 24% of the page (this is an estimate, and depends on the screen being used).
What is semantic structure?
Semantic HTML is the use of HTML to reinforce the meaning (semantics) of the information in webpages and web applications rather than merely to define its presentation or look.


---
layout: page
bodyClass: articles
title: Articles
excerpt: Articles and their declension
---
Definite article
{% include declension.html
declension=site.data.articles.der
%}
Indefinite article
{% include declension.html
declension=site.data.articles.ein
%}
Negative article
{% include declension.html
declension=site.data.articles.kein
%}
Possessive articles
{% include declension.html
declension=site.data.articles.mein
%}
{% include declension.html
declension=site.data.articles.dein
%}
{% include declension.html
declension=site.data.articles.sein
%}
{% include declension.html
declension=site.data.articles.ihr
%}
{% include declension.html
declension=site.data.articles.unser
%}
{% include declension.html
declension=site.data.articles.euer
%}


BrewDog Offering To Buy All Office Workers Two Pints To Reclaim The Lunch Hour
by Cameron FrewDo you spend lunchtimes at your desk, probably still working as you chomp on a sandwich or sausage roll? Well, BrewDog are about to change that.
The Scottish beer company is combating ‘always-on’ culture by offering to buy office workers two pints for them and a colleague.
The move to help people reclaim their lunch hour comes after research found that 77% of UK office workers spend their entire lunch hour at their desk (as one of those people, this is music to my ears).
The research also found that 57% of UK office workers welcome a return to the lunchtime pint as a means of ‘switching off’ and only 11% of employees said they are actively encouraged to socialise away from the office.
Hence BrewDog’s new Out of Office campaign, aimed at giving people some time to escape their workplace and enjoy some downtime and socialising with their colleagues.
From Monday to Friday this week (November 25-29), the craft beer company is offering to buy two pints of beer (including alcohol-free) for them and a colleague every lunchtime between 12.00-14.00 across all their 52 UK bars.
The campaign comes after the research also revealed that nearly half of office workers (49%) think popping down the pub or heading to a bar at lunchtime is a key part of ‘British culture’.
Alas, two thirds of office workers feel it’s no longer the done thing, saying they feel it’s frowned upon by their bosses.
Alongside Happiness is Here author Susanna Newsonen, BrewDog are bringing back the lunchtime pint.
 Going totally offline and away from your desk at lunch is vital in helping you to recharge your batteries and better deal with stress.
People taking part in BrewDog Out of Office will find they come back into the office feeling happier and more productive. It’s also a great way to better connect with your colleagues and build more positive relationships with them.
If you’re interested in participating in the campaign, all you need to do is visit BrewDogOOO.com and input your email address. Soon after, you’ll receive a QR code with suggested ‘out of office’ messages to use.
When you arrive at your pub of choice, just show your ‘out of office’ and you’ll secure a nice, cold, crisp pint to enjoy on your lunch break.
BrewDog co-founder James Watt said:
Leaving your desk at lunch has become frowned upon in many workplaces, most people make do with shovelling down a sad sandwich at their desk while they crack on with work. That’s not a break, and it’s certainly not good for productivity or our mental and physical health.
BrewDog is making a stand and we’re putting our money where our mouth is, buying pints for the people to help them get away from their desks for a true break at lunch. It’s time to make a change. BrewDog Out of Office encourages people to take back that hour that is rightfully theirs.
Our aim to be inclusive, so we are offering the choice of alcohol-free Punk AF or Nanny State as well as the classic Punk IPA or Lost Lager. This initiative isn’t about encouraging people to be unprofessional, it’s about reclaiming lunchtime for the people.
Looks like this is going to be a great week.
After graduating from Glasgow Caledonian University with an NCTJ and BCTJ-accredited Multimedia Journalism degree, Cameron ventured into the world of print journalism at The National, while also working as a freelance film journalist on the side, becoming an accredited Rotten Tomatoes critic in the process. He’s now left his Scottish homelands and took up residence at UNILAD as a journalist.


--- layout: post index: 4 tags: Karos title: The First Launch of K'aros excerpt: "After waiting in the queue line and entering the station building, it is now time to take a seat on K'aros. In this blog post we follow the coaster along its first section and experience the effect-loaded first launch." reddit: "https://www.reddit.com/r/PlanetCoaster/comments/cm8l1u/the_first_launch_of_karos_grindlheim_blog_update/" planco-forum: "https://forums.frontier.co.uk/threads/grindlheim-an-immersive-theme-world-inspired-by-klugheim-at-phantasialand.506455/post-7931432" number-of-images: 12 image-descriptions: - The train leaves the station... - ...and slowly meanders down the first section of track leading up to the first launch. - Here K'aros crosses over Ri'kh before entering the launch building. - We are coming close to the launch now. - Just one more curve through this themed room. - As the train is slowed down on the straight launch section, the machine's pistons and gears start to move. - This is what the start of the launch section looks fom the outside. Underneath the launch lies the queue line of K'aros. Above, people can have a cup of coffee on the observation deck, but more of that in a later update. - As the train slowly moves forward, the steam and sound effects start to play as well. - Close to the launch the effects intensify and the machine starts to crack and rumble. - And finally, the train is shot out of the launch building with a big blast of smoke. - On the left side a path runs parallel to the launch so people can feel the power of the coaster when standing right next to it. - The journey through Grindlheim begins. ---
In the last two blog posts we had a look at the queue line and station building of K'aros. Today we follow the K'aros experience by taking a seat in the coaster and checking out its first section starting at the station and leading up to the first launch.
As ususal with Grindlheim, inspiration of this section was taken from Taron at Phantasialand. The coaster slowly leaves the station and makes its way around the first corners until it stops on the launch track. A synchronized soundtrack is being played throughout this part to raise the suspense before the sudden launch. I'm doing the same with K'aros but I added some extra effects. In Grindlheim the launches of K'aros are being powered by a huge steam machine located in the queue line. Right before the launch, dozens of triggered steam and sound effects raise the suspense even further. Getting all the effects to work was not an easy task because I ran into issues with the game not triggering some of them properly.
Please check out the gallery below for a picture-tour of K'aros' first launch.


Elang Documentation & Quick Start¶
The 5-min Guide to Word Embeddings¶
If you have a Word2Vec
model and would like to generate a 2-dimensional word embedding visualization, this can be done through the plot2d
function:
from elang.plot.utils import plot2d
from gensim.models import Word2Vec
model = Word2Vec.load("path.to.model")
plot2d(model)
The default method for dimensionality reduction (to obtain exactly two dimensions) is T-SNE. This, and the other parameters can be specified as optional parameters.
For example, you may not wish to plot all the words in your Word2Vec model, and only wish to see a list of words. This can be done using the words
parameter;
You may optionally wish to bring attention to a small subset of words within the plot, and this can be done using the targets
parameter.
We will also specify methods to be “PCA” instead of “T-SNE” (default), resulting in the following function call:
from elang.plot.utils import plot2d
list_of_words_to_appear = ["bca", "mandiri", "uob", "algoritma", "airbnb", ..., "emiten"]
plot2d(model,
# method for dimensionality reduction
method="TSNE",
# only show following words in the final plot
words=list_of_words_to_appear,
# target words are given special emphasis in the final plot
targets=['uob', 'mandiri','bca']
)
elang also includes visualization methods to help you visualize a user-defined k number of neighbors to each words.
When draggable
is set to True
(default False
), you will obtain a legend that you can move around in the resulting plot.
from elang.plot.utils import plotNeighbours
model = Word2Vec.load("path.to.model")
words = ['bca', 'hitam', 'hutan', 'pisang', 'mobil', "cinta", "pejabat", "android", "kompas"]
plotNeighbours(model,
words,
method="TSNE",
k=15,
draggable=True)
The code plots the 15 nearest neighbors for each word in the supplied words argument. It then renders the plot with a draggable legend.
Just like the case of plot2d, it uses “T-SNE” as the default method for dimensionality reduction. This can be overriden via the method
parameter.
The 5-min Guide to NLP Preprocessing¶
Elang comes with a number of pre-processing functions to make cleaning data in Bahasa Indonesia a little easier.
The remove_*
group of functions parses a string and eliminate any occurrences of words in a pre-determined list (negative list).
from elang.word2vec.utils import *
x = "Oh ya, saya sudah pernah ke Hutan Ingatan Pasar Seni, Bandung, Senin 25 Maret kemarin. Tempat ini bagus anjir."
x = remove_stopwords_id(x)
# x: "Saya pernah ke Hutan Ingatan Pasar Seni, Bandung, Senin 25 Maret kemarin. Tempat bagus anjir."
x = remove_region_id(x)
# x: "Saya pernah ke Hutan Ingatan Pasar Seni, Senin 25 Maret kemarin. Tempat bagus anjir."
x = remove_calendar_id("Hutan Ingatan Pasar Seni, Bandung, Senin 25 Maret")
# x: "Saya pernah ke Hutan Ingatan Pasar Seni kemarin. Tempat bagus anjir."
x = remove_vulgarity_id(x)
# x: "Saya pernah ke Hutan Ingatan Pasar Seni kemarin. Tempat bagus."
FAQs¶
1. Can I use the library to visualize my word embeddings trained using English corpus (instead of Indonesian)?¶
Answer:
Yes. There are no inherent assumptions about the model. plot2d
and plotNeighbors
will take a Word2Vec model and a supplied list of words and generate your plot.
In practice, your model may be trained from a mixed set of languages and they won’t matter as long as the underlying representation for each word vector remain consistent.


Understanding the Record Lifecycle
This guide describes the lifecycle of Record objects in SproutCore. By referring to this guide you will be able to:
- Understand the statuses and substatuses that a record can be in at any given time.
- Understand how a record progresses from creation to modification to destruction in a SproutCore database.
1 - The Lifecycle of a SproutCore Record
SproutCore can manage much of the work of reading from, writing to, creating and destroying the records in your application. It does this by applying a set of statuses onto records, and using these to track the status of individual records as they travel through the application. You can access the status of a given record, at any time using the status function. Internally, this status is stored as an integer - normally then, you will test to find out what status you are in by using the & operator for statuses, and the === operator for substatuses.
In the examples in this section, we assume that you are using an application called MyApp, with a model called MyModel that inherits from SC.Record and has a single string attribute: 'name'. There is a data store stored inside Myapp.store. Changes are not set to auto-commit.
record = MyApp.MyModel.find(MyApp.store, 1);
// Assuming this record already exists in the store...
record.status; // Meaningless numeric response
record.status & SC.Record.READY; // Will return a 'truthy' value, because the record is in a ready status
record.status & SC.Record.DESTROYED; // Will return a 'non-truthy' value, because the record is not in a destroyed status
record.status === SC.Record.READY_CLEAN; // Will return true, because it is in the READY_CLEAN substatus
record.status === SC.Record.READY_DIRTY; // Will return false, because while the record is READY, it is not in the READY_DIRTY substatus
record.name = "A different name"; // The record has now been altered, and should be marked as 'dirty' until it is saved to the server
record.status & SC.Record.READY; // Will still return true, since the record is still in a ready status
record.status === SC.Record.READY_CLEAN; // Will return now return false - the record is no longer the READY_CLEAN substatus...
record.status === SC.Record.READY_DIRTY; // ... because it's now dirty, so this statement is now true
The status (like all the record attributes) is not stored in the SC.Record object directly, but rather in its entry in the data store. This means if you have two instances of the same record, they will always have the same status.
record = MyApp.MyModel.find(MyApp.store, 1);
the_same_record = MyApp.MyModel.find(MyApp.store, 1);
record.status === SC.Record.READY_CLEAN; // true
the_same_record.status === SC.Record.READY_CLEAN; // also true
record.name = "A completely different name from previous";
record.status === SC.Record.READY_CLEAN; // false
the_same_record.status === SC.Record.READY_CLEAN; // also false
record.status === SC.Record.READY_DIRTY; // true
the_same_record.status === SC.Record.READY_DIRTY; // also true
You will not necessarily see the behaviour above if you are using the fixtures store. This is because the fixtures store commits changes immediately - there is no remote store, so records are never 'dirty'. For more details see the guide on 'Using Fixtures'.
2 - An Overview of the Statuses
There are three major record statuses in SproutCore:
- SC.Record.READY
- SC.Record.BUSY
- SC.Record.DESTROYED
There are, additionally, two other statuses for special conditions a record might find itself in:
- SC.Record.EMPTY
- SC.Record.ERROR
When your application creates a new record or first tries to download an existing one from the server, the record will first enter the SC.Record.EMPTY status, as it's various attributes and connection to the data store are initialized. This status is transitory, however, and the record should transition out more or less instantly.
When the record is initialized and exists in a stable, waiting condition in the local data store, it will be in the SC.Record.READY status. This is the normal "resting" status of a record.
From this status, changes could be made to the record - while these changes are being committed back to the server (or for other reasons) the record would be in the SC.Record.BUSY status.
Finally, if the record is deleted, it would enter the SC.Record.DESTROYED status. At any point, if an error occurred or the record was left in an unstable status, it may enter the SC.Record.ERROR status. Let's go into each of these statuses - and any attached sub-statuses - in more detail.
3 - SC.Record.EMPTY Status
The SC.Record.EMPTY status is a special, transitory status that a record is in before it has been fully initialized. SC.Record.EMPTY is something of a non-status - it is, essentially, the status a record is in before it has been initialized with a status. As a result, you should never see a record in this status unless you are really digging into the low-level mechanics of how SC.Record initializes records.
The SC.Record.EMPTY status can only be entered when the new object is created - it can never be reentered again.
A record in the SC.Record.EMPTY status can exit into any of the other four major statuses, depending on how the record was created. Most commonly, however, a record will take one of two paths forward:
- The application is creating a new record that is not in the datastore already. In this case, the record will be transitioned into the SC.Record.READY status (more specifically, the SC.Record.READY_NEW substatus).
- The application is retrieving an existing record from the remote data source. In this case, the record will begin the record retrieval process, and enter the SC.Record.BUSY status (more specifically, the SC.Record.BUSY_LOADING)
Keep in mind - status is stored not in the record object, but in the datastore. So, creating a new SC.Record object may not necessarily involve putting the record through SC.Empty at all. SC.Empty is the status a record is in when it is first created in the data store - when you create a brand new record, or look up a record from the remote data source for the first time. In both of these cases, it is highly unlikely that you'll even see the SC.Empty status. If this is confusing, please see the sample record biographies, later in this document.
4 - SC.Record.READY Status
The SC.Record.READY status is the 'normal' status for a record to exist in. If a record is in ready status, this implies three things:
- The record is fully initialized
- The record is in an internally consistent status
- The record is available to be read from and written to
The SC.Record.READY status has three sub-statuses, which denote the condition that the record is in in relation to the remote database.
4.1 - SC.Record.READY_NEW
A record in this status was created in memory but has not yet been committed back to the server. If you commit changes on your store, this record will be sent to the data source automatically. Likewise if you destroy this record it will simply be removed from memory without notifying the server. The data source also cannot modify records while they are in this status since the remote data source doesn't even know that the record exists, presumably. A record enters SC.Record.READY_NEW, normally, from SC.Record.EMPTY, because a new record has been created from scratch. A record in SC.Record.READY_NEW, then, will either eventually be destroyed, transitioning to SC.Record.DESTROYED_CLEAN, or it will be committed, transitioning it to SC.Record.BUSY_CREATING.
4.2 - SC.Record.READY_CLEAN
This is the status of a record that has been retrieved by the store, and for which there are no pending changes to be committed back to the remote data source.  Likewise, if your data source receives a notification from the server that this record has changed, it can modify this record without any kind of error since no changes would be lost. Records should normally, then, only enter the SC.Record.READY_CLEAN status from SC.Record.BUSY - after having been retrieved from or updated against the server. Clean records frequently transition into many different statuses:
If the application triggers a destroy action, it enters the SC.Record.DESTROYED_DIRTY status (where its destruction can then be synced to the server, as described later in this document)
- If the application writes new data to the object, it will move to the SC.Record.READY_DIRTY status, where it will await syncing to the server
- If an application requests a refresh of the application against the remote data source, it will enter the SC.Record.BUSY_REFRESH_CLEAN status
4.3 - SC.Record.READY_DIRTY
This is the status of a record that exists on the server but has since been modified in your SproutCore application. It has pending changes that will be sent to the server when you commit changes in your store. If your remote data source tries to modify this record, it will throw an error since local changes would be lost - the changes from the server could overwrite the changes that have been made on the local application. The only exception to this would be if a refresh is called, manually, by the application. In this case, the record will enter the SC.Record.REFRESH_DIRTY status, where it overwrites the attributes in the record with whatever the remote data source returns. A record enters the SC.Record.READY_DIRTY, thus, only from SC.Record.READY_CLEAN, because a change is made to the clean record. It should normally only exit this status in two ways:
- The data source initiates a sync of the record to the remote data source - this will transition the record into the SC.Record.BUSY_COMMITTING status.
- The application chooses to delete the record before it ever commits the changes. The record would then be moved to SC.Record.DESTROYED_DIRTY.
A record in SC.Record.REFRESH_DIRTY is analogous to an unsaved file: the user has made changes, but you haven't saved them yet. It's important to make sure that you take care of these records before an application closes, and/or make sure that the user is aware of the difference between a record they've changed that hasn't been saved, and a record they've changed that HAS been saved
5 - SC.Record.BUSY Status
The SC.Record.BUSY status is a catch-all status for times when a record should be inaccessible to other processes, usually because it is being synchronized with a remote data source. A record in the SC.Record.BUSY status has the following properties:
- It is in the midst of a blocking process - when the process completes (successfully or unsuccessfully), it is responsible for moving the record into a non-busy status.
- It cannot have new data written to it.
- It cannot be updated from the remote data source. Changes can be made by the application (upstream changes), but downstream changes from the remote server will raise an error.
- It can still be read from.
When writing your application, forgetting the SC.Record.BUSY is a pretty common error. If you write functions that try to change record objects, it is important to ensure these objects are not busy. This is usually accomplished by testing for the status of a record before writing, by closely controlling when and how server communication occurs, or both.
SC.Record.BUSY has six substatuses.
5.1 - SC.Record.BUSY_LOADING
When you first get a record from the store, it will usually be in the BUSY_LOADING status. This means that the record did not exist in the store and the remote data source has not yet returned any data to the data store. All properties will be empty at this point. Normally, this means the status is entered from the SC.Record.EMPTY status. Once data is retrieved from the server for this record, it will transition to SC.Record.READY_CLEAN.
5.2 - SC.Record.BUSY_CREATING
When a brand new, unsaved record (i.e., one in SC.Record.READY_NEW) is committed to the server for the first time, it is placed in this status, to indicate that it is in the process of being remotely created, for the first time. This means, of course, that this status is entered only from the SC.Record.READY_NEW status. Once the server responds to indicate a successful save, it will transition into SC.Record.READY_CLEAN. If the creation fails, the record will return to SC.Record.READY_NEW.
5.3 - SC.Record.BUSY_COMMITTING
When an existing record's changes are being committed to the store, it enters this status. The status is transitioned into from the SC.Record.READY_DIRTY status (since there are changes that need to be committed). If the commit succeeds, it is transitioned in the SC.Record.READY_CLEAN status. If it fails, it will transition back into SC.Record.READY_DIRTY.
5.4 - SC.Record.BUSY_REFRESH_DIRTY
A record in this status has local changes but you asked the data source to reload the data from the server anyway (that is, it was in the SC.Record.READY_DIRTY status, but you ordered a manual refresh). If the server update completes successfully, it will replace any local changes with a fresh copy from the server, and transition the record into the SC.Record.READY_CLEAN status. If the refresh fails, the record will return to the SC.Record.READY_DIRTY status and its data will be left intact.
5.5 - SC.Record.BUSY_REFRESH_CLEAN
A record in this status has no local changes but you asked it to reload the data from the server in case there have been any changes remotely (that is, it was in SC.Record.READY_CLEAN, but you requested a manual refresh of the record). When the server finishes, success or failure, this record will return to the SC.Record.READY_CLEAN status. If the request succeeds, it should overwrite the data in the record with any changes from the remote data source.
5.6 - SC.Record.BUSY_DESTROYING
A record in this status was destroyed in the store and now is being destroyed on the server as well. Once the destroy has completed, the record will become SC.Record.DESTROYED_CLEAN. If the destroy fails, it will become SC.Record.DESTROYED_DIRTY again.
6 - SC.Record.DESTROYED Status
This status is the opposite of SC.Record.READY_NEW: your application has ordered that the record be destroyed, and it's marked as such in the local data store. Because this change is asynchronous - the record is not, normally, REMOVED from the data store, it's just marked as destroyed. These records have the following properties:
- The record has been marked for destruction, or has been destroyed. It should not be treated as viable data
SC.Record.DESTROYED has two substatuses, which denote whether or not the destruction has been pushed back to the remote data source.
6.1 - SC.Record.DESTROYED_CLEAN
A record in this status has been destroyed both in the store and on the server. There are two ways for a record two enter this status:
- The record was in +SC.Record.READY_NEW, and was destroyed. Because this means it was never written to the server in the first place, the record is clean, and has reached a final status.
- The record was in the process of destruction (that is, it was in SC.Record.BUSY_DESTROYING), and the server returned a response implying that the destruction was completed successfully. The record has reached a final status
Because a clean, destroyed record has been removed from the remote data source, it is normally most appropriate to treat it as being in a final status. Normally, you'll not transition it into any other status.
6.2 - SC.Record.DESTROYED_DIRTY
A record in this status was destroyed in the store by the application, but the action of destruction has not been yet synchronized to the server. A record in this status should be static, and cannot accept changes from the server unless the server also puts it into a destroyed status. A record enters this status because it was in an SC.Record.READY_CLEAN or SC.Record.READY_DIRTY status, and was marked for deletion by your application. Committing the record to the SC_BUSY_DESTROYING status for final destruction on the remote data source is the only way to escape this status.
7 - SC.Record.ERROR Status
A record in this status has, for some reason, entered an unstable status. Any status can, theoretically, transition into the SC.Record.ERROR status, if an illegal function is called, or if the record enters a data status that's ambiguous. In general, once a record ends up in this status, it should be considered suspect and destroyed - the application should regenerate or re- retrieve this data manually.
8 - Biography of a Record
Some of these life-paths are easier to follow by examining them through the lifecycle of an actual record. In these examples, we'll presume that we have an application called BabyNames, that we are building to contain a dictionary of baby names, corresponding to a short passage describing their origin and meaning. As such, there is a model in our application, BabyNames.Entry that inherits from SC.Record. It has two string attributes, a name and a meaning. Our application has a data store, BabyNames.store that connects to an external data source.
8.1 - Creating a Record
Our crack research team has been reading and came across an intriguing new baby name to add to our database, and thus are creating a new BabyNames.Entry record, like so:
var astrolabe = BabyNames.store.createRecord{
BabyNames.Entry, {
name: "Astrolabe"
}
}
Initially, upon creation of this record, there will be an instant in which the record is in the SC.Record.EMPTY status, but we will never see this change, because immediately the createRecord process will set an explicit status for the record. The record that gets returned will be ready for us to work with:
astrolabe.status & SC.Record.EMPTY; // this is false, by the time the record is returned
astrolabe.status & SC.Record.READY; // returns a 'truthy' value
astrolabe.status === SC.Record.READY_NEW; // returns true - we have a new, unsaved record
For the purposes of this discussion, we're not going to explore the work of assigning an id to the record. For a discussion of this process, see the guide on Using Records and the Data Store
This record, currently, exists only in our local data store. If our research team closed their browser, this entry would be lost, forever.
Because this is a new record, there's no worries about clean versus dirty status. So, when we set attributes on the record, there is no status change:
astrolabe.status === SC.Record.READY_NEW; // returns true
astrolabe.meaning = "A scientific instrument used anciently in astronomy."
astrolabe.status === SC.Record.READY_NEW; // still returns true
Now, our researcher is happy with the record. As such, she commits all the pending changes to the server.
BabyNames.store.commitRecords();
At this point, a complex series of events happens. First, the store searches through the records it holds, and extracts every record that is awaiting a commit (any record, in other words, that is in SC.Record.READY_NEW, SC.Record.READY_DIRTY, or SC.Record.DESTROYED_DIRTY). Our record certainly fits these criteria, so it is committed to the server to update. Now, presuming that our remote data source is a bit, slow, it may a take a while for this commit to complete. If this is true, we can now see our record in a different status:
astrolabe.status & SC.Record.READY; // False: Our record is now in a different status...
astrolabe.status & SC.Record.BUSY; // True: ...busy, as it tries to work with the server
astrolabe.status === SC.Record.BUSY_CREATING; // True: Specifically, it is trying to create this record
After a moment, though, our server accepts our new record creation, and we've completed the process of creating this record.
astrolabe.status & SC.Record.READY; // True
astrolabe.status === SC.Record.READY_CLEAN; // Our record is saved to the server
So, what happens if the record does NOT save? In large part, this depends on how we write the code for our data store. It is often a good idea, for instance, to have our data store watch for, say, validation errors, and handle these by simply canceling the create and, perhaps, setting a validation errors field on the record. Then, the record would go back into SC.Record.READY_NEW. The point is, deciding how these responses will be handled is up to us when we write the code to communicate with the remote data source. For more information on programming a data source, see the guide on "Connecting with a DataSource"
8.2 - Modifying a record
Our crack researchers get a call from the company president: they've had a complaint from a customer that they don't believe Astrolabe is a real name. The researchers decide that perhaps they should put in some more information on the name, to reassure people that the name has, truly, been used in the past. First, of course, they'll need to look up the record in the data source. (For the purposes of this document, rather than getting into the fine art of writing queries, we will assume that the id of the Astrolabe record is 12345)
var astrolabe = BabyNames.Entry.find(BabyNames.store, 12345);
Now, in the background, the application will look in it's data store and check: do I have a record here that has id 12345? If we had, for instance, not closed our browser between the creation example above and now (presuming we wrote our data source correctly), then we should already have this record pulled into our data source, somewhere. At this point, the record will just be returned immediately. No status needs to be set on it at all, because the status, you'll remember is stored in the data source, not in the record object, so it will just have the status we last left it with - in this case, SC.Record.READY_CLEAN. Setting up an SC.Record instance is, essentially, nothing more than setting up a pointer to an entry in the data store.
But, what if we HAD closed the application, and this record had not been used in our application since we reopened it? In this case, the record for Astrolabe exists on the remote server, but not in our local data store. The data store will look up the ID 12345, and find nothing, and then proceed to check the remote data source instead, requesting the record from there. Presume, again, that our remote server is a tad slow. In this case, now when we look at the record, what will be it's status?
astrolabe.status & SC.Record.READY; // false - this record is not ready for us to use, yet
astrolabe.status & SC.Record.BUSY; // true - the record is busy because it's waiting for a server response
astrolabe.status === SC.Record.BUSY_LOADING; // true - the record is busy loading the record from the server
Eventually, the server responds, and hands us the record we're looking for. At this point, the data store will write this data into it's internal storage, and you have a valid, clean record to use:
astrolabe.status & SC.Record.READY; // true - it's ready for you to work with, now!
astrolabe.status & SC.Record.READY_CLEAN; // true - since we just retrieved it from the server, it's clean, and needs no updates
Again, the behavior surrounding a failure - like, if we were searching for a record that doesn't exist on the remote data source - is up to us. We may choose, for instance, to move the record into the SC.Record.ERROR status - since we are treating it like an existing record, but it does not, in fact, exist, it's in an inconsistent status, right? We'd code this into our data source.
At this point, the researcher sits back and thinks, trying to remember where she DID hear the name 'Astrolabe'. This takes her about 15 minutes, before she remembers, so she figures, maybe she ought to just check and make sure no one has altered this record elsewhere, in the meantime, so she refreshes it from the server
BabyNames.store.find(BabyNames.Entry, 12345);
astrolabe.status & SC.Record.READY; // False: Now we're busy talking to the server again...
astrolabe.status === SC.Record.BUSY_REFRESH_CLEAN; // True: looks like the server is still busy letting us know the changes
// wait a few minutes
astrolabe.status & SC.Record.READY; // TRUE: Good, refresh is complete!
astrolabe.status === SC.Record.READY_CLEAN; // TRUE: And, our record is clean and ready
Now, it's time to make our changes:
astrolabe.meaning = "A scientific instrument used anciently in astronomy. Used by famous medieval lovers Abelard and Heloise, as the name for their illegitimate son. Not a very popular name, since."
Again, though, this record has not been saved. If we close the browser, now, we'll lose our hard work.
astrolabe.status & SC.Record.READY; // True: Yes, the record is still ready for work
astrolabe.status === SC.Record.READY_CLEAN; // False: But it has not been synced to the server!
astrolabe.status === SC.Record.READY_DIRTY; // True: The record exists on the server, but we've made changes that haven't been saved yet!
So, we'd better save our changes, now.
BabyNames.store.commitRecords();
Since our record is in the SC.Record.READY_DIRTY status, it should be included in the refreshes, so it will start pushing these changes to the server.
astrolabe.status & SC.Record.READY; //False - not ready, busy talking to the server
astrolabe.status & SC.Record.BUSY; //True
astrolabe.status === SC.Record.BUSY_COMMITTING; // Since we're updating an existing record, we are in the SC.Record.BUSY_COMMITING status, this time
// Now we wait a minute for the process to finish...
astrolabe.status & SC.Record.READY; // True, so communication with the server completed...
astrolabe.status === SC.Record.READY_CLEAN; // Also true, so communication was successful!
So, now our record has been updated.
Frequently, you'll want to check on the status of your record's update. This guide follow this pattern very frequently - testing against SC.Record.READY will tell you whether the record is done communicating, first. But this, of course, doesn't necessarily mean communication SUCCEEDED, just that it COMPLETED. The second step is to test if the status equal SC.Record.READY_CLEAN. Additionally, in your application, it may be a good idea to also test for SC.Record.ERROR, to see if the communication was aborted in an unstable status.
8.3 - Deleting a Record
As crazy as this may seem, apparently there isn't a lot of popular interest in the name 'Astrolabe' these days. The CEO has a chat with the research team about this, and asks them to remove the record since it's just cluttering up the database and making customers roll their eyes. This process is pretty simple. The find should follow the exact same process as in the update task. The only difference is the actual process of deletion.
astrolabe.status & SC.Record.READY; //True: So, it's available to be worked with
astrolabe.status == SC.Record.READY_CLEAN; //True: There's no changes hanging around on the record. Let's go ahead and delete it.
astrolabe.destroy();
Now, the destroy status does not make any change, immediately on the server, it essentially just marks the record for deletion. So, if we closed the application now, then the record would still be in the database, next time we reopened it.
astrolabe.status & SC.Record.DESTROYED; //True, this record is destroyed in our local data store...
astrolabe.status === SC.Record.DESTROYED_CLEAN; //False: ...but we haven't synced this deletion to the remote server yet!
astrolabe.status === SC.Record.DESTROYED_DIRTY; // True: record is marked to be deleted when we sync with our server
So, the next time we sync to the server, we'll go through a process more or less parallel to record creation.
BabyNames.store.commitRecords(); //So, since our record is in dirty, it should be included in the commits
astrolabe.status & SC.Record.BUSY; // True - looks like we're in the process of talking to the server
astrolabe.status === SC.Record.BUSY_DESTROYING; // True - we're busy destroying the record remotely
// So, lets wait and let the server finish
astrolabe.status & SC.Record.DESTROYED; // True - looks like we finished trying to delete the record.
astrolabe.status === SC.Record.DESTROYED_CLEAN; // True - and it looks like the deletion succeeded!
9 - Changelog
- March 30, 2011: Fixed some spelling and clarity issues by Jason Gignac
- September 3, 2013: converted to Markdown format for DocPad guides by deeDude
- September 11, 2013: added Changelog section by Topher Fangio


– Cuypers is appointed the official architect of the Rijksmuseum complex. Soon afterwards, preparatory work commences on the Stadhouderskade building site designated by the City of Amsterdam.
– On 13 January, the first pile is driven for the main building. Meanwhile, the approved design is constantly being altered. Not only are the details of the façades and the towers modified, but the two entrances are also moved from the passageway to the side of the building facing the city centre, and a last-minute decision is made to expand the low-ceilinged basement into a full-sized souterrain.
The museum building is opened in the absence of King Willem III, who refuses to attend in protest against the final design. When the museum opens in the new building, it adopts the name of the organization that has managed the state art collection since 1815: the Rijksmuseum. – The Oefenschool van de Rijksnormaalschool (Training School) opens in its temporary location, a wooden building in the garden.
– The Night Watch belongs to the city of Amsterdam, and it been the highlight of the Rijksmuseum collection since 1886
The first exhibition is held in the Fragment Building, an addition to the museum in the west garden. The exhibition areas in the souterrain are opened to the public.The passageway is opened to traffic.
The decorations in the Great Hall and the Gallery of Honour are completed.
Large-scale renovation takes place under museum director Frederik Schmidt-Degener in cooperation with the Rijksgebouwendienst (Government Buildings Agency; Rgd), which assumes the management of the building in 1924. After the death of Pierre Cuypers, a start is made on removing and covering up some of the decorations in consultation with Joseph Cuypers, who succeeds his father as official Rijksmuseum architect. The settled wooden floors of the two courtyards are replaced by new reinforced concrete floors supported by piles. After the renovation fewer works are exhibited in each gallery, and hence more storage space is needed. Parts of the lower level and the attic spaces are used for this purpose.
Architect F.A. Eschauzier (1889-1957) oversees the phased redecoration of the main building and the Drucker extension. He responds to demands for an up-to-date style of presentation, better technical systems, and expanded public services. The east courtyard is converted into an exhibition area. Despite a shortage of space, the Rijksmuseum acquires the collection of the Museum for Asian Art. Exhibition galleries are set up for this collection on the lower level of the Drucker extension. The director’s villa ceases to be used as an official residence; after the renovation, it becomes an administrative building. In the garden, the labyrinth from the original design is replaced by a French formal garden.
W.G. (Wim) Quist (b. 1930) is appointed by the Rgd as the Rijksmuseum architect.
The renovated Night Watch Gallery and Gallery of Honour are reopened. Some later renovations have been reversed, and the result comes closer to the effect of the original spaces. A few of Cuypers’ paintings have been reconstructed, and the Night Watch is returned to its original location. To view the changes compared to other renovations of the museum please click here
– The new Rijksmuseum is listed as one of nine Major Projects in the architecture policy document Shaping the Netherlands, co-authored by the ministries of culture, planning, transport, and agriculture. – The Rgd presents Het Nieuwe Rijksmuseum, structuurplan 2000, a plan for the new Rijksmuseum that encompasses the entire complex, including the former Security Institute. This plan lays the groundwork for architect selection. – Chief Government Architect Wytze Patijn and his successor Jo Coenen jointly announce the seven architecture firms competing for the role of lead architect for the project. – The firms invited to participate in the competition are asked to develop a vision for the renovation of the Rijksmuseum that fleshes out the basic principle ‘Back to Cuypers’, understood to mean ‘Continue with Cuypers’.
– Cruz y Ortiz arquitectos is selected as the lead architect for the new Rijksmuseum. The firm’s design concept garners praise from the assessment committee for its clarity and sound logistical solutions, partly on account of the passage connecting the two covered courtyards.
– The main Rijksmuseum building closes and the removal of the collection begins. The South Wing is renovated and temporarily converted into an independent museum. Many works of art are put in storage in Lelystad for the duration of the renovation.
– The renovation of the main building and the construction of the Asian Pavilion, designed by Cruz y Ortiz, is completed. Now work on the interior can begin. The Entrance Building and Teekenschool are also finished. After the construction equipment has been cleared