\section{Introduction}
\label{sec:introduction}

Various researchers have noted that the APIs of widely-used libraries are often hard to learn and use correctly \cite{Robillard:IEEESOftware2009,Robillard:ESE2011,ZibranEtAlWCRE2011,WangGodfrey:MSR2013}. 
Prevention of erroneous use is particularly crucial in Data Science (DS): Programs that crash at run-time for a reason that could have been detected at compile-time are generally a waste of time, resources, and, ultimately, money -- and downright prohibitive after running hours or days. Therefore, static safety would be a major benefit for every Data Scientist, novice or master.
Unfortunately, most DS pipelines are written in Python, and use Python DS libraries \cite{kaggle2021KaggleMachine}. For Python, static type checking is only available via external linters like mypy\footnote{\url{https://github.com/python/mypy}}, which are, however, inapplicable if the code is not annotated with \keyword{type hints} \cite{TypingSupportType}.
Unfortunately, many libraries do not provide type hints, and no Python linter we know of is able to handle advanced type concepts 
like schema types for tabular datasets, 
which would help a lot in typical DS applications. 



 

A radically different approach is taken by systems that hide the DS APIs behind a visual pipeline specification language (e.g. RapidMiner Studio \cite{mierswaYALERapidPrototyping2006}), a wizard-like interface (e.g. RapidMiner Go \cite{rapidminerGo}), or a configuration language (e.g. Ludwig \cite{ludwig}).
However, wizards and configuration languages can only provide guidance \cite{reimannAchievingGuidanceApplied2020a} and safety at the expense of limiting what can be expressed. Visual programming can offer full expressiveness, but when a pipeline gets complex, a graph representation quickly gets cluttered, and textual languages excel. Depending on the skills of the user and the use case, different representations are optimal.
However, no known system provides
visual \emph{and} textual ways to specify
DS pipelines. 

 
\label{sec:research-questions}

In this context, we explore the following research questions:
\begin{itemize}
     \item RQ 1: 
        How can we make the most widely used libraries for DS usable  even by programming novices?
       
    \item RQ 2: 
        Is it possible to design a language that is expressive enough to implement any DS pipeline but simple enough to be learned and used correctly, even by novices? 
       
    \item RQ 3: 
        Which classes of errors are not caught by conventional type systems (especially those of Python linters) but occur frequently in DS development?
    \item RQ 4: 
        Which
        type concepts could help and should be part of a dedicated DS language?
    \item RQ 5:
        How can a DS language ensure statically safe use of DS libraries written in dynamic languages
    \item RQ 6: 
        How can we make
        integration of existing Python DS libraries easy, even for third parties?
   
   
    \item RQ 7: 
        How can we leverage the built-in error prevention of graphical languages without sacrificing the flexibility of textual code? 
        %
       
\end{itemize}


The solution suggested in this paper is \safeds{}, a statically typed domain specific language (DSL) \cite{enwiki:dsl} for specifying DS pipelines. It provides: 
\begin{enumerate}
    \item 
        A simple mechanism to integrate existing Python DS libraries in a type-safe way (Sec. \ref{sec:static-checking}). 
    \item
        A well-designed standard library that includes the best available DS libraries (briefly outlined in Sec. \ref{sec:prototype}). 
    \item 
        Support for third-party developers to extend the standard library by integrating their own features (Sec. \ref{sec:checking-external-code}).


   \item 
        A seamless bidirectional transition between graphical and textual representation, which are just different views of the same abstraction, the DSL (Sec. \ref{sec:different-views}). 
\end{enumerate}



\section{State of the Art}
\label{sec:state-of-the-art}

According to the 2021 Kaggle DS survey \cite{kaggle2021KaggleMachine}, over 84\% of almost 26.000 respondents use Python regularly, making it the most commonly used DS programming language. 
According to the survey, the most-often used libraries for DS are 
Matplotlib \cite{Hunter:2007},
Seaborn \cite{Waskom2021},
and Plotly \cite{plotly}
for data visualization, 
Pandas \cite{The_pandas_development_team_pandas-dev_pandas_Pandas} for data processing, and 
\sklearn{} \cite{scikit-learn, sklearn_api},
TensorFlow \cite{tensorflow2015-whitepaper},
Keras \cite{chollet2015keras},
PyTorch \cite{NEURIPS2019_9015},
and XGBoost  \cite{Chen:2016:XST:2939672.2939785}
for ML\footnote{Each group is sorted by descending usage percentages\cite{kaggle2021KaggleMachine}}.
All are implemented in Python, emphasizing its role in DS development. 

Nevertheless, there are noteworthy suggestions for DS pipeline development beyond Python:
Ludwig \cite{ludwig} allows users to build a DS pipeline with a YAML \emph{configuration file}. Users must specify input and output features, and can optionally alter some parts of the pipeline, such as data preprocessing.

External domain specific languages are another solution. The list of DSLs used for DS in 2016 \cite{portugalPreliminarySurveyDomainSpecific2016} includes \emph{textual DSLs} like Pig Latin \cite{olstonPigLatinNotsoforeign2008} for data processing and \emph{visual DSLs} like \cite{breukerModelDrivenEngineeringBig2014} for the representation of probabilistic models. Since then, other tools appeared, e.g. BiDaML \cite{khalajzadehBiDaMLSuiteVisual2019, khalajzadehBiDaMLPracticeCollaborative2021}, which offers five diagrammatic notations for different stages of the development of a DS pipeline. Another popular tool is RapidMiner Studio (formerly YALE) \cite{mierswaYALERapidPrototyping2006}, which offers a visual DSL to quickly, and
safely build a DS pipeline.

Finally, some tools use \emph{wizards} to guide users step by step through the development of a DS pipeline, for example RapidMiner Go \cite{rapidminerGo}.
To overcome the limited flexibility of a wizard, users can export the final result to RapidMiner Studio.

\cite{keryFutureNotebookProgramming2020} extends Jupyter Notebook \cite{kluyverJupyterNotebooksPublishing} by additional GUI elements like a spreadsheet view to inspect data. When users manipulate the data in these elements, corresponding code is added to the notebook. However, changes to the generated code can in general not be reflected in the GUI, making the transition between graphical and textual view one-way only.

These ideas can be combined with \emph{AutoML} \cite{heAutoMLSurveyStateoftheart2021} to automatically derive parts of 
a DS pipeline from the initial data.

An overview of free tools for data mining can be found in \cite{jovicOverviewFreeSoftware2014} and a more thorough survey of the DS landscape in \cite{nguyenMachineLearningDeep2019}. Even taking this larger context into account, no existing tool provides a comprehensive solution to 
the majority of our research questions, let alone all of them.



\section{A Language for Data Science Pipelines}
\label{sec:dsl}

A \emph{DS pipeline} is code that performs data loading, data preparation, model training, and hyperparameter optimization to produce a model that is ready for use. 
It conserves the steps necessary to reproduce the same model from the same data, without the data analysis and model quality assessment steps that developers performed to decide how to build the pipeline. 

The main aim of our work is to support pipeline developers of varying skill levels, including absolute programming and DS novices. For this, assembling DS pipelines should be easy to learn and use, but hard to misuse. Any solution must provide a small and consistent API (to ease learning and correct use), which must be easily extensible (to take advantage of the evolution of DS libraries). Extension must be possible and easy, also for third-party library developers.



\paragraph*{RQ 1} Given that the main DS libraries are implemented in Python, their use by programming novices for building pipelines is out of question. Even for experienced programmers, Python libraries that lack type annotations for linters are easy to misuse \cite{reimannImprovingLearnabilityMachine2022}. To make the wealth of DS libraries (Sec. \ref{sec:state-of-the-art}) usable by novices and prevent misuse by users of all skill levels we propose to reuse these libraries via Safe-DS, a DSL for DS that does the heavy lifting. 


\paragraph*{RQ 2} This raises the question, which language features are necessary for unrestricted pipeline development and which must be avoided to ease learning and use by novices. Because pipeline development is not about algorithm implementation but mainly a sequence of invocations of already implemented library functions, the set of necessary language features is simple. 
Fig. \ref{fig:pipeline-example} shows the start of a \safeds{} pipeline using the popular ``Titanic'' example.

\begin{figure}[ht]
    \centering
    \includegraphics[width=.48\textwidth]{img/listing_pipeline.pdf}
    \caption{The start of a \safeds{} pipeline to predict survival during the Titanic accident.}
    \label{fig:pipeline-example}
\end{figure}

The keyword \keyword{pipeline} is the entry point into a \safeds{} program. 
The one in our example is called \code{predictTitanicSurvival}. 
A pipeline contains a sequence of statements, usually assignments\footnote{The only other statement, besides assignments, is the expression statement, which evaluates an expression for its side effects, ignoring  results.}. 
The right-hand side of each assignment contains the expression to evaluate. An expression can be a literal (\code{\str{Titanic}}), a reference to a variable (\code{titanic}), or a call. A call invokes a global function, like \code{loadDataset}, or a method, like \code{keepColumns}. We refer to methods and global functions as \keyword{processes}. Calls can contain further expressions as arguments.

Processes can take lambdas (anonymous, parameterized sequences of statements) as arguments, to configure parts of their behavior. For example, a general \code{transform} function that changes values in one column of a tabular dataset needs to know how to actually change these values. Such higher-order processes are mainly used for data processing functions and rarely needed otherwise.

Notably, in Safe-DS there are no conditionals, loops, or recursive calls. These are encapsulated in the library functions invoked in the pipeline (e.g. hyperparameter search). Decisions and iterations by the developer (reflect on the outcome of the current pipeline, modify it, and restart it) happen outside the pipeline.
The lack of loops and conditionals makes the language easy to learn.
It also eases thorough static checking (Sec. \ref{sec:static-checking}) because data flows linearly through the program.


\section{Static Checking}
\label{sec:static-checking}

Supporting safety during the development of a DS pipeline is one of our key aims. For this, detecting errors \emph{statically}, that is before programs are run, is crucial, since the execution of DS pipelines can take hours or even days on large datasets. Achieving static checking, raises the research questions RQ 3 to RQ 6.

\subsection{Type Problems}
\label{sec:type-problems}


The empirical evaluation of \cite{reimannImprovingLearnabilityMachine2022} showed that basic typing features of Python linters already catch many common errors. However, we also identified typical problems that go beyond conventional type systems and are particularly frequent or important in a DS context (RQ 3).

\paragraph*{Lack of range restrictions} We found that the documentation of a library often expresses range restriction, e.g. ratios must be a numeric value between 0 and 1.

\paragraph*{Broken dataset access} When processing tabular datasets by adding, removing or renaming columns, it is easy to forget updating some operations that still access a changed column, because column names are just strings and the type system does not know about their special semantics. Neither does it know how to infer the type of columns.  

\paragraph*{Broken order} Machine learning processes must often be called in a specific order (e.g. training before prediction). This simple example could be solved at API level by distinguishing the types \code{UntrainedModel} and \code{TrainedModel}, where only the latter would have a \code{predict} method. However, this would exclude integration of existing libraries that mutate an untrained model in place (e.g. \sklearn{}), would assume redesign of the library, and would bloat the number of types. 





\subsection{Type System}
\label{sec:type-system}

Having identified challenging type problems (RQ 3) we now introduce the solutions provided by Safe-DS (RQ 4).  


\paragraph*{Basic type system} 
To capture the basic aspects of DS APIs, the Safe-DS type system supports class types, enum types, function types (which include types for parameters and results), union types, subtyping, and genericity. 

\paragraph*{Refined types} As a way to express range restrictions, Safe-DS
additionally includes refined types, which restrict the legal set of values of an existing type via some constraint. For parameters that have a refined type, only expressions that can be evaluated to a constant value by the compiler can be passed. Otherwise, the compiler would not be able to verify the constraint of the refined type.

\paragraph*{Schema inference and checking} To address the broken dataset access problem (Sec. \ref{sec:type-problems} ) \safeds{} checks correctness of schema-sensitive operations, without having to run the \emph{entire} DS pipeline up to that point:
(1) When a tabular dataset is loaded from external sources, e.g. a CSV file, the compiler infers its initial schema by analyzing the data.
(2) For any data processing function invoked in the pipeline, 
    the compiler inductively infers the schema of the resulting dataset 
    from the schemata of the input datasets and a description 
    of the function's \keyword{schema effects}. 
Our schema effect system \cite{lucassenPolymorphicEffectSystems1988a, marinoGenericTypeandeffectSystem2009a} captures any combination of column addition, removal, renaming, or type change.
In addition, we support two basic schema checks: (1) Ensuring a column with a given name exists. (2) Ensuring a column has a given type. Based on the results of schema inference, the compiler executes such checks statically, thus ensuring early feedback to the user.


\paragraph*{Order checking} 

To solve order issues, we support behavior protocols \cite{plasilBehaviorProtocolsSoftware2002} in interface descriptions.
These are regular expressions that describe legal call orders, with processes instead of characters as basic tokens. For instance, we can specify that on any model, the process that trains the model must be called before the one that makes predictions. Using this information, we can ensure statically that the call order in pipelines is correct. 


\subsection{Integrating External Code}
\label{sec:checking-external-code}
\label{sec:stubs}

So far, we have not explained, how the Safe-DS compiler can know at all which functions are available, and how it can statically check calls to functions implemented in a language that does not provide a static type system (RQ 5).

Hard-coding a fixed set of processes into the grammar of \safeds{} would clearly preclude extensibility. This could be avoided by analyzing the code of external libraries, collecting contained type hints or inferring the types of arguments and results while the user develops a pipeline. However, many Python DS libraries do not provide type hints and type inference is not generally possible for Python.

Therefore, we designed \safeds{} as a combination of the \emph{pipeline language} described in Sec. \ref{sec:dsl} with an additional \keyword{stub language}. Each stub is an accurate description of the fully typed interface of a particular Python function. 
Fig. \ref{fig:stub-example} shows the stub for the \code{loadDataset} process. The \keyword{fun} keyword indicates that \code{loadDataset} is a function. 
For each parameter (in parentheses) or result (after arrow) we list its name and type, separated by a colon. 

\begin{figure}[ht]
    \centering
    \includegraphics[width=.48\textwidth]{img/listing_stub.pdf}
    \caption{The \safeds{} stub for the \code{loadDataset} function.}
    \label{fig:stub-example}
\end{figure}

Pipelines can now invoke the specified \code{loadDataset} process (Fig. \ref{fig:pipeline-example}). The \safeds{} compiler checks type-correctness of invocations, by comparing the types of arguments (inferred) with those specified in the respective stub. 
Any process call for which there is no matching stub is assumed to be wrong, leading to an error message. 

Stubs tell the \safeds{} compiler all it needs to know for type checking. Therefore, the stub language also includes ways to specify refined types, schema effects, schema checks, and behaviour protocols. For example, the process \code{get\_column} in Fig. \ref{fig:schema-check-example}, which returns the column of the \code{table} with the given \code{name}, specifies that a column called \code{name} must exist in \code{table}. 

\begin{figure}[ht]
    \centering
    \includegraphics[width=.48\textwidth]{img/schema_check.pdf}
    \caption{A function stub with a schema check to ensure a column called \code{name} exists in the \code{table}.}
    \label{fig:schema-check-example}
\end{figure}

\subsection{Semi-Automated Stub Generation and Implementation}
\label{sec:automated-stub-generation}


With the stub language, implementers of new machine learning models or other useful functions can integrate them into \safeds{}. However, integrating an entire library manually would be tedious and a major deterrent to the adoption of \safeds{}. Thus, we need to explain how this process can be eased (RQ 6). 

Our approach adopts and evolves the API-Editor approach of \cite{reimannImprovingLearnabilityMachine2022}.
In a first step, types are inferred for Python code based on type hints in the code \cite{TypingSupportType} or, if type hints are not available, documentation.
For the cases that cannot be inferred automatically,
an easy to use GUI is provided. It also supports specification of changes to the API (e.g. removal of declarations, renaming, and creation of parameter objects). Some of these suggestions can even be inferred automatically. Once the improved and simplified API is finalized, \safeds{} stubs and their corresponding implementations as Python wrappers around the original library are created automatically. 





This way, large libraries can quickly be integrated into \safeds{}, with only limited manual intervention for cases where the documentation is not accurate enough. 


\section{Different Views}
\label{sec:different-views}

So far, we discussed representing \safeds{} pipelines as text (Sec. \ref{sec:dsl}). However, this is only suitable for people with some programming skills, which does not apply to everyone involved in the development of DS pipelines. 
To encourage people without programming skills to try solving DS problems, and understand solutions of experts, we provide different views onto DS pipelines, tailored for specific skillsets (RQ 7). 


Fig. \ref{fig:graphical_pipeline} shows a possible translation of the pipeline from Fig. \ref{fig:pipeline-example} into a visual DSL, which still offers full flexibility but improves safety by preventing syntax errors.
The surrounding box corresponds to the pipeline, with the name shown in the top right corner. The rounded rectangles inside correspond to the invoked processes, with circles for parameters (left) and results (right). This information is retrieved from the stub of the process (Sec. \ref{sec:checking-external-code}). 

Arrows represent dataflow between processes, 
e.g. the arrow from the \code{loadDataset} to the \code{keepColumns} process says that the result of the first process is input to the second one. 
The name shown above an arrow corresponds to the name of a variable in the textual DSL.
The rightmost arrow is attached to a ``blank'' circle since this value is never used but we still want to show the name of the variable that stores it, 
for easier mapping to the textual representation and as a hint to possibly unfinished pipelines.
Literal inputs (e.g. strings) are hidden in the graph representation 
to avoid clutter.
They can be made visible by clicking on a process. This approach helps keep the graph small. It is also used in RapidMiner Studio \cite{mierswaYALERapidPrototyping2006}.

\begin{figure}[ht]
    \centering
    \includegraphics[width=.43\textwidth]{img/graphical_pipeline.pdf}
    \caption{Graphical representation of the pipeline from Fig. \ref{fig:pipeline-example}. Literals are only revealed when clicking on a node, to keep the graph small.}
    \label{fig:graphical_pipeline}
\end{figure}

Existing systems (Sec. \ref{sec:state-of-the-art}) provide either a code or a graph view of a pipeline, or in case of \cite{keryFutureNotebookProgramming2020} both but only with a fully defined transition from graph to code view. 
We 
think that supporting both views, including seamless transitions in both direction, is crucial, to let each person involved in the development of a DS pipeline use the view best suited for them, and even switch between the views, as needed. It would, for example, facilitate collaboration between domain experts and experienced Data Scientists \cite{maoHowDataScientists2019, zhangHowDataScience2020}: A domain expert could prototype an initial pipeline using the visual DSL and then pass it to a Data Scientist, who elaborates and refines it in the textual DSL. Afterwards, the Data Scientist could explain his solution to the domain expert using again the visual DSL, where they could make further changes together.

The switch between different views is only possible if the target view is at least as expressive as the source view. 
Since we want to be able to seamlessly switch between textual and graphical view back and forth, this implies that both representations must be equivalent. Our approach trivially guarantees this, otherwise hard to achieve, prerequisite. In \safeds{}, text and graph are just two different notations for the \emph{same} language.


\section{Prototype}
\label{sec:prototype}

\paragraph*{Language}
We implemented a prototype of \safeds{}\footnote{\url{https://github.com/lars-reimann/Safe-DS}} with Xtext\footnote{\url{https://www.eclipse.org/Xtext}}. At the time of writing, it contains the complete textual syntax for the pipeline and stub languages, scoping, automated formatting of \safeds{} code, generation of Python code, and a vast amount of static checks. 
Static checking currently supports the basic type system without genericity and refined types. It also supports type inference in pipelines. Checking of schema effects is work in progress and behavior protocols are future work. We use Xtext to create a language server \cite{lspcontributorsOfficialPageLanguage}, which we make available for Visual Studio Code\footnote{\url{https://code.visualstudio.com}} via a small extension\footnote{\url{https://marketplace.visualstudio.com/items?itemName=larsreimann.safe-ds}}. 

\paragraph*{Metamodel}
The core of the language is the \keyword{metamodel}, the abstract representation of the language elements, also known as the \keyword{abstract syntax} of the language. Individual DS pipelines are instances of this metamodel. The static checks are implemented on top of the metamodel. Each view of the DSL (Sec. \ref{sec:different-views}) defines a transition to and from the metamodel. First, this allows the static analysis to be defined independently from a concrete view, which only needs to display errors to the user in an appropriate form. Second, we avoid having to define and implement transitions between all current and future views. The metamodel is implemented with EMF\footnote{\url{https://www.eclipse.org/modeling/emf}}.


Fig. \ref{fig:different-views} shows the relation between the textual and graphical view and the metamodel. It also shows the one-way transition from the metamodel to Python code generated by the compiler.



\begin{figure}[ht]
    \centering
    \includegraphics[width=.40\textwidth]{img/different_views_simple.pdf}
    \caption{Interaction between different views (blue) with the metamodel to connect them.}
    \label{fig:different-views}
\end{figure}

\paragraph*{Standard library}
Moreover, we started work on the Safe-DS standard library. So far, it includes basic functionality to work with tabular data (using pandas \cite{The_pandas_development_team_pandas-dev_pandas_Pandas} internally), visualize this data (using seaborn \cite{Waskom2021}), and train and evaluate ML models (using scikit-learn \cite{scikit-learn, sklearn_api}). An experimental version of the corresponding Python implementation is available on PyPI\footnote{\url{https://pypi.org/project/safe-ds}}. We will discuss the standard library in more detail in a future publication.

 
 
\section{Discussion}
\label{sec:discussion}

\paragraph*{Accuracy of stubs}
The static analysis requires that stubs accurately describe the preconditions of the corresponding Python implementation. A mismatch can either cause valid DSL programs to be rejected or invalid DSL programs to be executed, leading to runtime exceptions or undefined behavior. We aim to reduce the chance of such mismatches by automatically creating stubs for Python declarations where possible (Sec. \ref{sec:automated-stub-generation}). Furthermore, users only need to work with the stub language when they want to integrate functionality that is missing from the standard library. For the standard library itself, we pay particular attention to the accuracy of the stubs.

\paragraph*{Omission of language concepts}
While the language itself contains neither loops nor conditionals, nothing stops a user from implementing higher-order \code{if} or \code{while} functions in Python and integrating these into \safeds{} with its stub language. We would treat these like any other function, only using the information from the stubs for static analysis or the representation in the graphical view. On the one hand, this means we ignore the semantics of the if and while constructs for static analysis. On the other hand, such constructs are not needed in the first place since we offer domain specific abstractions like a \code{transform} function for tabular datasets, which encapsulate any loops or conditionals in the Python implementation and use features of the DSL like the schema checking (Sec. \ref{sec:type-system}) for domain specific validation.


\section{Future Plans}
\label{sec:future-plans}

\paragraph*{Additional checks} We are working on completing the implementation of schema checking and will then add checking of call order using behavior protocols. We also look into whether we can extend \cite{reimannImprovingLearnabilityMachine2022} to partially infer schema effects and behavior protocols automatically from documentation.

\paragraph*{Standard library} In parallel, we are integrating additional features from Pandas \cite{The_pandas_development_team_pandas-dev_pandas_Pandas} for data preparation, seaborn for visualization \cite{Waskom2021}, and \sklearn{} \cite{scikit-learn, sklearn_api} for traditional machine learning, using the already implemented stub language (Sec. \ref{sec:checking-external-code}) and automated stub generation (Sec. \ref{sec:automated-stub-generation}). 

\paragraph*{Usability study} When a sufficient part of the standard library is implemented, we will conduct a quantitative and qualitative comparison of \safeds{} and Python for solving real world problems. 

\paragraph*{Different views} Currently, the available Visual Studio Code extension for \safeds{} only has a textual view. We plan to also implement a graphical view (Sec. \ref{sec:different-views}) using GLSP\footnote{\url{https://www.eclipse.org/glsp/}}. 


\section{Conclusions}
\label{sec:conclusion}

In this paper we focused on the needs of DS applications, rather than DS libraries. We presented \safeds{} (Sec. \ref{sec:dsl}), a
DSL for the development of DS pipelines. It combines the expressive power of Python DS libraries with safety via compile-time error detection.

Type-safe integration of Python code is based on creation of stubs (Sec. \ref{sec:checking-external-code}) that describe 
the interface of external functions, following largely automated type extraction from a library's code \emph{and} documentation (Sec. \ref{sec:automated-stub-generation}). 
The current implementation of \safeds{} already supports a full conventional static type system, with manifest types in the stubs, and type inference in DS pipelines. 
Adaptations of non-standard type concepts, such as effect systems (to check schema-conformance of operations on tabular datasets) and behaviour protocols (to check call order) can help deal with these issues statically, too (Sec. \ref{sec:type-system}). 


To enable DS pipeline development by people with limited programmings skills 
and foster collaboration of people with different skill levels, 
our approach supports seamless transition between textual and graphical incarnations of the language (Sec. \ref{sec:different-views}). 
Overall, we hope that \safeds{} will prove valuable to help novices
get started with Data Science.

\section*{Acknowledgments}

This work was partially funded by the Federal Ministry of Education and Research (BMBF), Germany under the Simple-ML project (grant 01IS18054). We are grateful for all former contributions to the Safe-DS GitHub repository, particularly with respect to schema checking and the standard library. We also thank the reviewers for their numerous constructive comments and suggestions.



\newpage

\bibliographystyle{IEEEtran}
