Repository: sktime/sktime
Pull request #5676 (2023-12-30 00:09:54)

&corradomio [ENH] config to turn off data memory in forecasters
This PR adds a boolean config field, `remember_data`, which can be used to turn off storing of `self._X` and `self._y`.

This was requested in discussion https://github.com/sktime/sktime/discussions/5545, and is enabled by https://github.com/sktime/sktime/pull/5590.

FYI @corradomio

Some open questions:

* this does not turn off storing of `self._yvec`, which can store data. Optimally, `VectorizedDF` also has a similar option - this is feasible, as most logic only uses indices internally.
* this will not work if the estimator is cloned, which seems illogical. In `scikit-base`, we should probably clone configs - potentially addressed by https://github.com/sktime/sktime/pull/5676

fkiraly: fails due to bug in `scikit-base`: https://github.com/sktime/skbase/issues/258

Patch:
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 0cf8d0a3e..5b9b9183e 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -6,7 +6,7 @@ Contributors
 <!-- Please add your badges to .all-contibutorsrc -->
 
 <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
-[![All Contributors](https://img.shields.io/badge/all_contributors-250-orange.svg)](#contributors)
+[![All Contributors](https://img.shields.io/badge/all_contributors-251-orange.svg)](#contributors)
 <!-- ALL-CONTRIBUTORS-BADGE:END -->
 
 This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -240,7 +240,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
     </tr>
     <tr>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/rnkuhns"><img src="https://avatars0.githubusercontent.com/u/26907244?v=4?s=100" width="100px;" alt="Ryan Kuhns"/><br /><sub><b>Ryan Kuhns</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=RNKuhns" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=RNKuhns" title="Documentation">📖</a> <a href="#tutorial-RNKuhns" title="Tutorials">✅</a> <a href="#example-RNKuhns" title="Examples">💡</a> <a href="#ideas-RNKuhns" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3ARNKuhns" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sktime/sktime/commits?author=RNKuhns" title="Tests">⚠️</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/achieveordie"><img src="https://avatars.githubusercontent.com/u/54197164?v=4?s=100" width="100px;" alt="Sagar Mishra"/><br /><sub><b>Sagar Mishra</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=achieveordie" title="Tests">⚠️</a></td>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/achieveordie"><img src="https://avatars.githubusercontent.com/u/54197164?v=4?s=100" width="100px;" alt="Sagar Mishra"/><br /><sub><b>Sagar Mishra</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Aachieveordie" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=achieveordie" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=achieveordie" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://sajay.online"><img src="https://avatars2.githubusercontent.com/u/25329624?v=4?s=100" width="100px;" alt="Sajaysurya Ganesh"/><br /><sub><b>Sajaysurya Ganesh</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sajaysurya" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=sajaysurya" title="Documentation">📖</a> <a href="#design-sajaysurya" title="Design">🎨</a> <a href="#example-sajaysurya" title="Examples">💡</a> <a href="#ideas-sajaysurya" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/commits?author=sajaysurya" title="Tests">⚠️</a> <a href="#tutorial-sajaysurya" title="Tutorials">✅</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/SamiAlavi"><img src="https://avatars.githubusercontent.com/u/32700289?v=4?s=100" width="100px;" alt="Sami Alavi"/><br /><sub><b>Sami Alavi</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=SamiAlavi" title="Code">💻</a> <a href="#maintenance-SamiAlavi" title="Maintenance">🚧</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/sssilvar"><img src="https://avatars.githubusercontent.com/u/16252054?v=4?s=100" width="100px;" alt="Santiago Smith Silva"/><br /><sub><b>Santiago Smith Silva</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sssilvar" title="Code">💻</a></td>
@@ -276,13 +276,14 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Vasudeva-bit"><img src="https://avatars.githubusercontent.com/u/70791259?v=4?s=100" width="100px;" alt="Vasudeva Kilaru"/><br /><sub><b>Vasudeva Kilaru</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Vasudeva-bit" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=Vasudeva-bit" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/victordremov"><img src="https://avatars.githubusercontent.com/u/32140716?s=100" width="100px;" alt="Viktor Dremov"/><br /><sub><b>Viktor Dremov</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=victordremov" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/ViktorKaz"><img src="https://avatars0.githubusercontent.com/u/33499138?v=4?s=100" width="100px;" alt="ViktorKaz"/><br /><sub><b>ViktorKaz</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=ViktorKaz" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=ViktorKaz" title="Documentation">📖</a> <a href="#design-ViktorKaz" title="Design">🎨</a></td>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/vnicholson1"><img src="?s=100" width="100px;" alt="Vincent Nicholson"/><br /><sub><b>Vincent Nicholson</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=vnicholson1" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/VyomkeshVyas"><img src="?s=100" width="100px;" alt="Vyomkesh Vyas"/><br /><sub><b>Vyomkesh Vyas</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=VyomkeshVyas" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=VyomkeshVyas" title="Documentation">📖</a> <a href="#example-VyomkeshVyas" title="Examples">💡</a> <a href="https://github.com/sktime/sktime/commits?author=VyomkeshVyas" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/wayneadams"><img src="https://avatars.githubusercontent.com/u/15034841?s=400&u=d717e9945910bcc844c5e64cd56d570c6cc4e8e6&v=4?s=100" width="100px;" alt="Wayne Adams"/><br /><sub><b>Wayne Adams</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=wayneadams" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/templierw/"><img src="https://github.com/templierw.png?s=100" width="100px;" alt="William Templier"/><br /><sub><b>William Templier</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=templierw" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/magittan"><img src="https://avatars0.githubusercontent.com/u/14024202?v=4?s=100" width="100px;" alt="William Zheng"/><br /><sub><b>William Zheng</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=magittan" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=magittan" title="Tests">⚠️</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/yairbeer"><img src="https://avatars.githubusercontent.com/yairbeer?s=100" width="100px;" alt="Yair Beer"/><br /><sub><b>Yair Beer</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=yairbeer" title="Code">💻</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/yairbeer"><img src="https://avatars.githubusercontent.com/yairbeer?s=100" width="100px;" alt="Yair Beer"/><br /><sub><b>Yair Beer</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=yairbeer" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/yann-hallouard/"><img src="https://avatars.githubusercontent.com/YHallouard?s=100" width="100px;" alt="Yann Hallouard"/><br /><sub><b>Yann Hallouard</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=YHallouard" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=YHallouard" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/yashlamba"><img src="https://avatars.githubusercontent.com/u/44164398?v=4?s=100" width="100px;" alt="Yash Lamba"/><br /><sub><b>Yash Lamba</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=yashlamba" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/xuyxu"><img src="https://avatars2.githubusercontent.com/u/22359569?v=4?s=100" width="100px;" alt="Yi-Xuan Xu"/><br /><sub><b>Yi-Xuan Xu</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=xuyxu" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=xuyxu" title="Tests">⚠️</a> <a href="#maintenance-xuyxu" title="Maintenance">🚧</a> <a href="https://github.com/sktime/sktime/commits?author=xuyxu" title="Documentation">📖</a></td>
@@ -291,9 +292,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/aa25desh"><img src="https://avatars1.githubusercontent.com/u/29518290?v=4?s=100" width="100px;" alt="aa25desh"/><br /><sub><b>aa25desh</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=aa25desh" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Aaa25desh" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/abandus"><img src="https://avatars2.githubusercontent.com/u/46486474?v=4?s=100" width="100px;" alt="abandus"/><br /><sub><b>abandus</b></sub></a><br /><a href="#ideas-abandus" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/commits?author=abandus" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/adoherty21"><img src="https://avatars.githubusercontent.com/u/52799751?s=400&v=4?s=100" width="100px;" alt="adoherty21"/><br /><sub><b>adoherty21</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Aadoherty21" title="Bug reports">🐛</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/bethrice44"><img src="https://avatars.githubusercontent.com/u/11226988?v=4?s=100" width="100px;" alt="bethrice44"/><br /><sub><b>bethrice44</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Abethrice44" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Abethrice44" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Tests">⚠️</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/bethrice44"><img src="https://avatars.githubusercontent.com/u/11226988?v=4?s=100" width="100px;" alt="bethrice44"/><br /><sub><b>bethrice44</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Abethrice44" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Abethrice44" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/big-o"><img src="https://avatars1.githubusercontent.com/u/1134151?v=4?s=100" width="100px;" alt="big-o"/><br /><sub><b>big-o</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=big-o" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=big-o" title="Tests">⚠️</a> <a href="#design-big-o" title="Design">🎨</a> <a href="#ideas-big-o" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Abig-o" title="Reviewed Pull Requests">👀</a> <a href="#tutorial-big-o" title="Tutorials">✅</a> <a href="#mentoring-big-o" title="Mentoring">🧑‍🏫</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/bobbys-dev"><img src="https://avatars.githubusercontent.com/bobbys-dev?s=100" width="100px;" alt="bobbys"/><br /><sub><b>bobbys</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=bobbys-dev" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/brettkoonce"><img src="https://avatars2.githubusercontent.com/u/11281814?v=4?s=100" width="100px;" alt="brett koonce"/><br /><sub><b>brett koonce</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=brettkoonce" title="Documentation">📖</a></td>
@@ -302,9 +303,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/chrisholder"><img src="https://avatars.githubusercontent.com/u/4674372?v=4?s=100" width="100px;" alt="chrisholder"/><br /><sub><b>chrisholder</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=chrisholder" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=chrisholder" title="Tests">⚠️</a> <a href="https://github.com/sktime/sktime/commits?author=chrisholder" title="Documentation">📖</a> <a href="#design-chrisholder" title="Design">🎨</a> <a href="#example-chrisholder" title="Examples">💡</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/danbartl"><img src="https://avatars.githubusercontent.com/u/19947407?v=4?s=100" width="100px;" alt="danbartl"/><br /><sub><b>danbartl</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Adanbartl" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=danbartl" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Adanbartl" title="Reviewed Pull Requests">👀</a> <a href="#talk-danbartl" title="Talks">📢</a> <a href="https://github.com/sktime/sktime/commits?author=danbartl" title="Tests">⚠️</a> <a href="#tutorial-danbartl" title="Tutorials">✅</a> <a href="#video-danbartl" title="Videos">📹</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/hamzahiqb"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=100" width="100px;" alt="hamzahiqb"/><br /><sub><b>hamzahiqb</b></sub></a><br /><a href="#infra-hamzahiqb" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/hiqbal2"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=100" width="100px;" alt="hiqbal2"/><br /><sub><b>hiqbal2</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=hiqbal2" title="Documentation">📖</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/hiqbal2"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=100" width="100px;" alt="hiqbal2"/><br /><sub><b>hiqbal2</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=hiqbal2" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/jesellier"><img src="https://avatars0.githubusercontent.com/u/51952076?v=4?s=100" width="100px;" alt="jesellier"/><br /><sub><b>jesellier</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=jesellier" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/jschemm"><img src="https://avatars.githubusercontent.com/u/81151346?v=4?s=100" width="100px;" alt="jschemm"/><br /><sub><b>jschemm</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=jschemm" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/kkoziara"><img src="https://avatars1.githubusercontent.com/u/4346849?v=4?s=100" width="100px;" alt="kkoziara"/><br /><sub><b>kkoziara</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=kkoziara" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Akkoziara" title="Bug reports">🐛</a></td>
@@ -313,9 +314,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/pabworks"><img src="https://avatars.githubusercontent.com/u/32725127?v=4?s=100" width="100px;" alt="pabworks"/><br /><sub><b>pabworks</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=pabworks" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=pabworks" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/xloem"><img src="?s=100" width="100px;" alt="patiently pending world peace"/><br /><sub><b>patiently pending world peace</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=xloem" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/raishubham1"><img src="https://avatars3.githubusercontent.com/u/29356417?v=4?s=100" width="100px;" alt="raishubham1"/><br /><sub><b>raishubham1</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=raishubham1" title="Documentation">📖</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/simone-pignotti"><img src="https://avatars1.githubusercontent.com/u/44410066?v=4?s=100" width="100px;" alt="simone-pignotti"/><br /><sub><b>simone-pignotti</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=simone-pignotti" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Asimone-pignotti" title="Bug reports">🐛</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/simone-pignotti"><img src="https://avatars1.githubusercontent.com/u/44410066?v=4?s=100" width="100px;" alt="simone-pignotti"/><br /><sub><b>simone-pignotti</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=simone-pignotti" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Asimone-pignotti" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/sophijka"><img src="https://avatars2.githubusercontent.com/u/47450591?v=4?s=100" width="100px;" alt="sophijka"/><br /><sub><b>sophijka</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sophijka" title="Documentation">📖</a> <a href="#maintenance-sophijka" title="Maintenance">🚧</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/sri1419"><img src="https://avatars2.githubusercontent.com/u/65078278?v=4?s=100" width="100px;" alt="sri1419"/><br /><sub><b>sri1419</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sri1419" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/tensorflow-as-tf"><img src="https://avatars.githubusercontent.com/u/51345718?v=4?s=100" width="100px;" alt="tensorflow-as-tf"/><br /><sub><b>tensorflow-as-tf</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=tensorflow-as-tf" title="Code">💻</a></td>
diff --git a/Makefile b/Makefile
index 32a475949..0697bdbf4 100644
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,13 @@ test: ## Run unit tests
 	cp setup.cfg ${TEST_DIR}
 	python -m pytest
 
+full_test: ## Run all tests
+	-rm -rf ${TEST_DIR}
+	mkdir -p ${TEST_DIR}
+	cp .coveragerc ${TEST_DIR}
+	cp setup.cfg ${TEST_DIR}
+	python -m pytest --only_changed_modules False
+
 test_without_datasets: ## Run unit tests skipping sktime/datasets
 	-rm -rf ${TEST_DIR}
 	mkdir -p ${TEST_DIR}
@@ -101,4 +108,4 @@ nb: clean
 
 dockertest:
 	docker build -t sktime -f build_tools/docker/$(PYTHON_VERSION).dockerfile .
-	docker run -it --name sktime sktime bash -c "make test"
+	docker run -it --name sktime sktime bash -c "make full_test"
diff --git a/docs/source/api_reference/classification.rst b/docs/source/api_reference/classification.rst
index b003e5097..ebb3d53b2 100644
--- a/docs/source/api_reference/classification.rst
+++ b/docs/source/api_reference/classification.rst
@@ -57,6 +57,7 @@ Deep learning
     :template: class.rst
 
     CNNClassifier
+    CNTCClassifier
     FCNClassifier
     LSTMFCNClassifier
     InceptionTimeClassifier
diff --git a/docs/source/api_reference/split.rst b/docs/source/api_reference/split.rst
index 493ae07c3..7786983b3 100644
--- a/docs/source/api_reference/split.rst
+++ b/docs/source/api_reference/split.rst
@@ -56,6 +56,14 @@ Time index splitter composition
 The following splitters are compositions that can be used to create
 more complex time index based splitting strategies.
 
+.. currentmodule:: sktime.split.compose
+
+.. autosummary::
+    :toctree: auto_generated/
+    :template: class.rst
+
+    Repeat
+
 .. currentmodule:: sktime.split
 
 .. autosummary::
diff --git a/sktime/classification/deep_learning/__init__.py b/sktime/classification/deep_learning/__init__.py
index c4f0c6bb7..7215a2bcd 100644
--- a/sktime/classification/deep_learning/__init__.py
+++ b/sktime/classification/deep_learning/__init__.py
@@ -1,6 +1,7 @@
 """Deep learning based classifiers."""
 __all__ = [
     "CNNClassifier",
+    "CNTCClassifier",
     "FCNClassifier",
     "InceptionTimeClassifier",
     "LSTMFCNClassifier",
@@ -13,6 +14,7 @@ __all__ = [
 ]
 
 from sktime.classification.deep_learning.cnn import CNNClassifier
+from sktime.classification.deep_learning.cntc import CNTCClassifier
 from sktime.classification.deep_learning.fcn import FCNClassifier
 from sktime.classification.deep_learning.inceptiontime import InceptionTimeClassifier
 from sktime.classification.deep_learning.lstmfcn import LSTMFCNClassifier
diff --git a/sktime/classification/deep_learning/cntc.py b/sktime/classification/deep_learning/cntc.py
new file mode 100644
index 000000000..900e98613
--- /dev/null
+++ b/sktime/classification/deep_learning/cntc.py
@@ -0,0 +1,287 @@
+"""Contextual Time-series Neural Classifier for TSC."""
+
+__author__ = ["James-Large", "TonyBagnall", "AurumnPegasus"]
+__all__ = ["CNTCClassifier"]
+from sklearn.utils import check_random_state
+
+from sktime.classification.deep_learning.base import BaseDeepClassifier
+from sktime.networks.cntc import CNTCNetwork
+from sktime.utils.validation._dependencies import _check_dl_dependencies
+
+
+class CNTCClassifier(BaseDeepClassifier):
+    """Contextual Time-series Neural Classifier (CNTC), as described in [1].
+
+    Parameters
+    ----------
+    n_epochs       : int, default = 2000
+        the number of epochs to train the model
+    batch_size      : int, default = 16
+        the number of samples per gradient update.
+    filter_sizes    : tuple of shape (2), default = (16, 8)
+        filter sizes for CNNs in CCNN arm.
+    kernel_sizes     : two-tuple, default = (1, 1)
+        the length of the 1D convolution window for
+        CNNs in CCNN arm.
+    rnn_size        : int, default = 64
+        number of rnn units in the CCNN arm.
+    lstm_size       : int, default = 8
+        number of lstm units in the CLSTM arm.
+    dense_size      : int, default = 64
+        dimension of dense layer in CNTC.
+    random_state    : int or None, default=None
+        Seed for random number generation.
+    verbose         : boolean, default = False
+        whether to output extra information
+    loss            : string, default="mean_squared_error"
+        fit parameter for the keras model
+    optimizer       : keras.optimizer, default=keras.optimizers.Adam(),
+    metrics         : list of strings, default=["accuracy"],
+
+    Notes
+    -----
+    Adapted from the implementation from Fullah et. al
+    https://github.com/AmaduFullah/CNTC_MODEL/blob/master/cntc.ipynb
+
+    References
+    ----------
+    .. [1] Network originally defined in:
+        @article{FULLAHKAMARA202057,
+        title = {Combining contextual neural networks for time series classification},
+        journal = {Neurocomputing},
+        volume = {384},
+        pages = {57-66},
+        year = {2020},
+        issn = {0925-2312},
+        doi = {https://doi.org/10.1016/j.neucom.2019.10.113},
+        url = {https://www.sciencedirect.com/science/article/pii/S0925231219316364},
+        author = {Amadu {Fullah Kamara} and Enhong Chen and Qi Liu and Zhen Pan},
+        keywords = {Time series classification, Contextual convolutional neural
+            networks, Contextual long short-term memory, Attention, Multilayer
+            perceptron},
+       }
+
+    Examples
+    --------
+    >>> from sktime.classification.deep_learning.cntc import CNTCClassifier
+    >>> from sktime.datasets import load_unit_test
+    >>> X_train, y_train = load_unit_test(split="train", return_X_y=True)
+    >>> X_test, y_test = load_unit_test(split="test", return_X_y=True)
+    >>> cntc = CNTCClassifier() # doctest: +SKIP
+    >>> cntc.fit(X_train, y_train) # doctest: +SKIP
+    CNTCClassifier(...) # doctest: +SKIP
+    """
+
+    _tags = {
+        "authors": ["James-Large", "Withington", "TonyBagnall", "AurumnPegasus"],
+        "maintainers": ["James-Large", "Withington", "AurumnPegasus"],
+        "python_dependencies": ["tensorflow", "keras-self-attention"],
+    }
+
+    def __init__(
+        self,
+        n_epochs=2000,
+        batch_size=16,
+        filter_sizes=(16, 8),
+        kernel_sizes=(1, 1),
+        rnn_size=64,
+        lstm_size=8,
+        dense_size=64,
+        callbacks=None,
+        verbose=False,
+        loss="categorical_crossentropy",
+        metrics=None,
+        random_state=0,
+    ):
+        _check_dl_dependencies(severity="error")
+
+        self.kernel_sizes = kernel_sizes  # used plural
+        self.filter_sizes = filter_sizes  # used plural
+        self.rnn_size = rnn_size
+        self.lstm_size = lstm_size
+        self.dense_size = dense_size
+        self.callbacks = callbacks
+        self.n_epochs = n_epochs
+        self.batch_size = batch_size
+        self.verbose = verbose
+        self.loss = loss
+        self.metrics = metrics
+        self.random_state = random_state
+        self._network = CNTCNetwork()
+
+        super().__init__(batch_size=batch_size, random_state=random_state)
+
+    def build_model(self, input_shape, n_classes, **kwargs):
+        """Construct a compiled, un-trained, keras model that is ready for training.
+
+        In sktime, time series are stored in numpy arrays of shape (d,m), where d
+        is the number of dimensions, m is the series length. Keras/tensorflow assume
+        data is in shape (m,d). This method also assumes (m,d). Transpose should
+        happen in fit.
+
+        Parameters
+        ----------
+        input_shape : tuple
+            The shape of the data fed into the input layer, should be (m,d)
+        n_classes: int
+            The number of classes, which becomes the size of the output layer
+
+        Returns
+        -------
+        output : a compiled Keras Model
+        """
+        from tensorflow import keras
+
+        metrics = ["accuracy"] if self.metrics is None else self.metrics
+        input_layer, output_layer = self._network.build_network(input_shape, **kwargs)
+
+        output_layer = keras.layers.Dense(units=n_classes, activation="softmax")(
+            output_layer
+        )
+
+        model = keras.models.Model(inputs=input_layer, outputs=output_layer)
+        model.compile(
+            loss=self.loss,
+            optimizer=keras.optimizers.Adam(),
+            metrics=metrics,
+        )
+        return model
+
+    def prepare_input(self, X):
+        """
+        Prepare input for the CLSTM arm of the model.
+
+        According to the paper:
+            "
+                Time series data is fed into a CLSTM and CCNN networks simultaneously
+                and is perceived differently. In the CLSTM block, the input data is
+                viewed as a multivariate time series with a single time stamp. In
+                contrast, the CCNN block receives univariate data with numerous time
+                stamps
+            "
+
+        Arguments
+        ---------
+        X: tuple of shape = (series_length (m), n_dimensions (d))
+            The shape of the data fed into the model.
+
+        Returns
+        -------
+        trainX: tuple,
+            The input to be fed to the two arms of CNTC.
+        """
+        import numpy as np
+        import pandas as pd
+        from tensorflow import keras
+
+        if X.shape[2] == 1:
+            # Converting data to pandas
+            trainX1 = X.reshape([X.shape[0], X.shape[1]])
+            pd_trainX = pd.DataFrame(trainX1)
+
+            # Taking rolling window
+            window = pd_trainX.rolling(window=3).mean()
+            window = window.fillna(0)
+
+            trainX2 = np.concatenate((trainX1, window), axis=1)
+            trainX2 = keras.backend.variable(trainX2)
+            trainX2 = keras.layers.Dense(
+                trainX1.shape[1], input_shape=(trainX2.shape[1:])
+            )(trainX2)
+            trainX2 = keras.backend.eval(trainX2)
+            trainX = trainX2.reshape((trainX2.shape[0], trainX2.shape[1], 1))
+        else:
+            trainXs = []
+            for i in range(X.shape[2]):
+                trainX1 = X[:, :, i]
+                pd_trainX = pd.DataFrame(trainX1)
+
+                window = pd_trainX.rolling(window=3).mean()
+                window = window.fillna(0)
+
+                trainX2 = np.concatenate((trainX1, window), axis=1)
+                trainX2 = keras.backend.variable(trainX2)
+                trainX2 = keras.layers.Dense(
+                    trainX1.shape[1], input_shape=(trainX2.shape[1:])
+                )(trainX2)
+                trainX2 = keras.backend.eval(trainX2)
+
+                trainX = trainX2.reshape((trainX2.shape[0], trainX2.shape[1], 1))
+                trainXs.append(trainX)
+
+            trainX = np.concatenate(trainXs, axis=2)
+        return trainX
+
+    def _fit(self, X, y):
+        """Fit the classifier on the training set (X, y).
+
+        Parameters
+        ----------
+        X : np.ndarray of shape = (n_instances (n), n_dimensions (d), series_length (m))
+            The training input samples.
+        y : np.ndarray of shape n
+            The training data class labels.
+
+        Returns
+        -------
+        self : object
+        """
+        if self.callbacks is None:
+            self._callbacks = []
+        y_onehot = self.convert_y_to_keras(y)
+        # Transpose to conform to Keras input style.
+        X = X.transpose(0, 2, 1)
+
+        check_random_state(self.random_state)
+        self.input_shape = X.shape[1:]
+        self.model_ = self.build_model(self.input_shape, self.n_classes_)
+        X2 = self.prepare_input(X)
+        if self.verbose:
+            self.model_.summary()
+        self.history = self.model_.fit(
+            [X2, X, X],
+            y_onehot,
+            batch_size=self.batch_size,
+            epochs=self.n_epochs,
+            verbose=self.verbose,
+            callbacks=self._callbacks,
+        )
+        return self
+
+    def _predict(self, X, **kwargs):
+        import numpy as np
+
+        probs = self._predict_proba(X, **kwargs)
+        rng = check_random_state(self.random_state)
+        return np.array(
+            [
+                self.classes_[int(rng.choice(np.flatnonzero(prob == prob.max())))]
+                for prob in probs
+            ]
+        )
+
+    def _predict_proba(self, X, **kwargs):
+        """Find probability estimates for each class for all cases in X.
+
+        Parameters
+        ----------
+        X : an np.ndarray of shape = (n_instances, n_dimensions, series_length)
+            The training input samples.
+
+        Returns
+        -------
+        output : array of shape = [n_instances, n_classes] of probabilities
+        """
+        import numpy as np
+
+        # Transpose to work correctly with keras
+        X = X.transpose((0, 2, 1))
+        X2 = self.prepare_input(X)
+        probs = self.model_.predict([X2, X, X], self.batch_size, **kwargs)
+
+        # check if binary classification
+        if probs.shape[1] == 1:
+            # first column is probability of class 0 and second is of class 1
+            probs = np.hstack([1 - probs, probs])
+        probs = probs / probs.sum(axis=1, keepdims=1)
+        return probs
diff --git a/sktime/forecasting/base/_base.py b/sktime/forecasting/base/_base.py
index 9c1bf1971..607a25ab8 100644
--- a/sktime/forecasting/base/_base.py
+++ b/sktime/forecasting/base/_base.py
@@ -115,6 +115,19 @@ class BaseForecaster(BaseEstimator):
         #  "joblib": uses custom joblib backend, set via `joblib_backend` tag
         #  "dask": uses `dask`, requires `dask` package in environment
         "backend:parallel:params": None,  # params for parallelization backend
+        "remember_data": True,  # whether to remember data in fit - self._X, self._y
+    }
+
+    _config_doc = {
+        "remember_data": """
+        remember_data : bool, default=True
+            whether self._X and self._y are stored in fit, and updated
+            in update. If True, self._X and self._y are stored and updated.
+            If False, self._X and self._y are not stored and updated.
+            This reduces serialization size when using save,
+            but the update will default to "do nothing" rather than
+            "refit to all data seen".
+        """,
     }
 
     def __init__(self):
@@ -1144,7 +1157,7 @@ class BaseForecaster(BaseEstimator):
             fh_orig = None
 
         # if no y is passed, the so far observed y is used
-        if y is None:
+        if y is None and self.get_config()["remember_data"]:
             y = self._y
 
         # we want residuals, so fh must be the index of y
@@ -1530,10 +1543,9 @@ class BaseForecaster(BaseEstimator):
         return self._check_X_y(X=X)[0]
 
     def _update_X(self, X, enforce_index_type=None):
-        if X is not None:
+        if X is not None and self.get_config()["remember_data"]:
             X = check_X(X, enforce_index_type=enforce_index_type)
-            if X is len(X) > 0:
-                self._X = X.combine_first(self._X)
+            self._X = update_data(self._X, X)
 
     def _update_y_X(self, y, X=None, enforce_index_type=None):
         """Update internal memory of seen training data.
@@ -1562,7 +1574,7 @@ class BaseForecaster(BaseEstimator):
         X : pd.DataFrame or 2D np.ndarray, optional (default=None)
             Exogeneous time series
         """
-        if y is not None:
+        if y is not None and self.get_config()["remember_data"]:
             # unwrap y if VectorizedDF
             if isinstance(y, VectorizedDF):
                 y = y.X_multiindex
@@ -1575,7 +1587,7 @@ class BaseForecaster(BaseEstimator):
             # set cutoff to the end of the observation horizon
             self._set_cutoff_from_y(y)
 
-        if X is not None:
+        if X is not None and self.get_config()["remember_data"]:
             # unwrap X if VectorizedDF
             if isinstance(X, VectorizedDF):
                 X = X.X_multiindex
@@ -1911,7 +1923,7 @@ class BaseForecaster(BaseEstimator):
         -------
         self : reference to self
         """
-        if update_params:
+        if update_params and self.get_config()["remember_data"]:
             # default to re-fitting if update is not implemented
             warn(
                 f"NotImplementedWarning: {self.__class__.__name__} "
@@ -1924,12 +1936,14 @@ class BaseForecaster(BaseEstimator):
             )
             # we need to overwrite the mtype last seen and converter store, since the _y
             #    may have been converted
+            mtype_last_seen = self._y_mtype_last_seen
             y_metadata = self._y_metadata
             _converter_store_y = self._converter_store_y
             # refit with updated data, not only passed data
             self.fit(y=self._y, X=self._X, fh=self._fh)
             # todo: should probably be self._fit, not self.fit
             # but looping to self.fit for now to avoid interface break
+            self._y_mtype_last_seen = mtype_last_seen
             self._y_metadata = y_metadata
             self._converter_store_y = _converter_store_y
 
@@ -2164,7 +2178,7 @@ class BaseForecaster(BaseEstimator):
                 fh = fh.to_absolute(self.cutoff)
             pred_var.index = fh.to_pandas()
 
-            if isinstance(self._y, pd.DataFrame):
+            if isinstance(pred_var, pd.DataFrame):
                 pred_var.columns = self._get_columns(method="predict_var")
 
             return pred_var
diff --git a/sktime/forecasting/naive.py b/sktime/forecasting/naive.py
index e1af8b4b6..df2ec152d 100644
--- a/sktime/forecasting/naive.py
+++ b/sktime/forecasting/naive.py
@@ -205,7 +205,7 @@ class NaiveForecaster(_BaseWindowForecaster):
             )
 
         # check window length
-        if self.window_length_ > len(self._y):
+        if self.window_length_ > len(y):
             param = "sp" if self.strategy == "last" and sp != 1 else "window_length_"
             raise ValueError(
                 f"The {param}: {self.window_length_} is larger than "
diff --git a/sktime/networks/cntc.py b/sktime/networks/cntc.py
new file mode 100644
index 000000000..d94ed71ec
--- /dev/null
+++ b/sktime/networks/cntc.py
@@ -0,0 +1,193 @@
+"""a combination of CCNN and CLSTM as feature extractors."""
+
+__author__ = ["James-Large", "Withington", "TonyBagnall", "AurumnPegasus"]
+
+from sktime.networks.base import BaseDeepNetwork
+from sktime.utils.validation._dependencies import (
+    _check_dl_dependencies,
+    _check_soft_dependencies,
+)
+
+
+class CNTCNetwork(BaseDeepNetwork):
+    """Combining contextual neural networks for time series classification.
+
+    Adapted from the implementation used in [1]
+
+    Parameters
+    ----------
+    kernel_size     : int, default = 7
+        specifying the length of the 1D convolution window
+    avg_pool_size   : int, default = 3
+        size of the average pooling windows
+    n_conv_layers   : int, default = 2
+        the number of convolutional plus average pooling layers
+    filter_sizes    : array of int, shape = (nb_conv_layers)
+    activation      : string, default = sigmoid
+        keras activation function
+    random_state    : int, default = 0
+        seed to any needed random actions
+
+    Notes
+    -----
+    Adapted from the implementation from Fullah et. al
+    https://github.com/AmaduFullah/CNTC_MODEL/blob/master/cntc.ipynb
+
+    References
+    ----------
+    .. [1] Network originally defined in:
+        @article{FULLAHKAMARA202057,
+        title = {Combining contextual neural networks for time series classification},
+        journal = {Neurocomputing},
+        volume = {384},
+        pages = {57-66},
+        year = {2020},
+        issn = {0925-2312},
+        doi = {https://doi.org/10.1016/j.neucom.2019.10.113},
+        url = {https://www.sciencedirect.com/science/article/pii/S0925231219316364},
+        author = {Amadu {Fullah Kamara} and Enhong Chen and Qi Liu and Zhen Pan},
+        keywords = {Time series classification, Contextual convolutional neural
+            networks, Contextual long short-term memory, Attention, Multilayer
+            perceptron},
+       }
+    """
+
+    _tags = {
+        "authors": ["James-Large", "Withington", "TonyBagnall", "AurumnPegasus"],
+        "maintainers": ["James-Large", "Withington", "AurumnPegasus"],
+        "python_dependencies": ["tensorflow", "keras-self-attention"],
+    }
+
+    def __init__(
+        self,
+        random_state=0,
+        rnn_layer=64,
+        filter_sizes=(16, 8),
+        kernel_sizes=(1, 1),
+        lstm_size=8,
+        dense_size=64,
+    ):
+        _check_soft_dependencies(
+            "keras-self-attention",
+            package_import_alias={"keras-self-attention": "keras_self_attention"},
+            severity="error",
+        )
+        _check_dl_dependencies(severity="error")
+
+        self.random_state = random_state
+        self.rnn_layer = rnn_layer
+        self.filter_sizes = filter_sizes
+        self.kernel_sizes = kernel_sizes
+        self.lstm_size = lstm_size
+        self.dense_size = dense_size
+
+        super().__init__()
+
+    def build_network(self, input_shape, **kwargs):
+        """Construct a network and return its input and output layers.
+
+        Arguments
+        ---------
+        input_shape: tuple
+            The shape of the data fed into the input layer, should be (m,d)
+
+        Returns
+        -------
+        input_layer: a keras layer
+        output_layer: a keras layer
+        """
+        from keras_self_attention import SeqSelfAttention
+        from tensorflow import keras
+
+        input_layers = []
+
+        # CNN Arm
+        input_layers.append(keras.layers.Input(input_shape))
+        input_layers.append(keras.layers.Input(input_shape))
+        self.dropout = 0.2
+
+        conv1 = keras.layers.Conv1D(
+            self.filter_sizes[0],
+            self.kernel_sizes[0],
+            activation="relu",
+            use_bias=True,
+            kernel_initializer="glorot_uniform",
+        )(input_layers[0])
+        conv1 = keras.layers.BatchNormalization()(conv1)
+        conv1 = keras.layers.Dropout(self.dropout)(conv1)
+        conv1 = keras.layers.Dense(
+            input_shape[1],
+            input_shape=(input_shape[0], keras.backend.int_shape(conv1)[2]),
+        )(conv1)
+
+        # RNN for CNN Arm (CCNN)
+        rnn1 = keras.layers.SimpleRNN(
+            self.rnn_layer * input_shape[1],
+            activation="relu",
+            use_bias=True,
+            kernel_initializer="glorot_uniform",
+        )(input_layers[1])
+        rnn1 = keras.layers.BatchNormalization()(rnn1)
+        rnn1 = keras.layers.Dropout(self.dropout)(rnn1)
+        rnn1 = keras.layers.Reshape((64, input_shape[1]))(rnn1)
+
+        # Combining CNN and RNN
+        conc1 = keras.layers.Concatenate(
+            axis=-2, name="contextual_convolutional_layer1"
+        )([conv1, rnn1])
+
+        # Final CNN for C-CNN (WHY)
+        conv2 = keras.layers.Conv1D(
+            self.filter_sizes[1],
+            self.kernel_sizes[1],
+            activation="relu",
+            kernel_initializer="glorot_uniform",
+            name="standard_cnn_layer",
+        )(conc1)
+        conv2 = keras.layers.Dense(
+            input_shape[1],
+            input_shape=(input_shape[0], keras.backend.int_shape(conv2)[2]),
+        )(conv2)
+        conv2 = keras.layers.BatchNormalization()(conv2)
+        conv2 = keras.layers.Dropout(0.1)(conv2)
+
+        # CLSTM Arm
+        input_layers.append(keras.layers.Input(input_shape))
+        lstm1 = keras.layers.LSTM(
+            self.lstm_size * input_shape[1],
+            return_sequences=False,
+            kernel_initializer="glorot_uniform",
+            activation="relu",
+        )(input_layers[2])
+        lstm1 = keras.layers.Reshape((self.lstm_size, input_shape[1]))(lstm1)
+        lstm1 = keras.layers.Dropout(self.dropout)(lstm1)
+        merge = keras.layers.Concatenate(
+            axis=-2, name="contextual_convolutional_layer2"
+        )([conv2, lstm1])
+
+        # Output calculation based on combination
+        avg = keras.layers.MaxPooling1D(pool_size=1, strides=None, padding="valid")(
+            merge
+        )
+        avg = keras.layers.Dropout(0.1)(avg)
+
+        # Adding self attention
+        att = SeqSelfAttention(
+            attention_width=10,
+            attention_activation="sigmoid",
+            name="Attention",
+            attention_type="multiplicative",
+        )(avg)
+        att = keras.layers.Dropout(0.1)(att)
+
+        # Adding ouutput MLP Layer
+        mlp1 = keras.layers.Dense(
+            self.dense_size, kernel_initializer="glorot_uniform", activation="relu"
+        )(att)
+        mlp1 = keras.layers.Dropout(0.1)(mlp1)
+        mlp2 = keras.layers.Dense(
+            self.dense_size, kernel_initializer="glorot_uniform", activation="relu"
+        )(mlp1)
+        mlp2 = keras.layers.Dropout(0.1)(mlp2)
+        flat = keras.layers.Flatten()(mlp2)
+        return input_layers, flat
diff --git a/sktime/split/compose/__init__.py b/sktime/split/compose/__init__.py
new file mode 100644
index 000000000..1799457e1
--- /dev/null
+++ b/sktime/split/compose/__init__.py
@@ -0,0 +1,6 @@
+"""Compositors for splitters."""
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+
+__all__ = ["Repeat"]
+
+from sktime.split.compose._repeat import Repeat
diff --git a/sktime/split/compose/_repeat.py b/sktime/split/compose/_repeat.py
new file mode 100644
index 000000000..30e816b3c
--- /dev/null
+++ b/sktime/split/compose/_repeat.py
@@ -0,0 +1,197 @@
+"""Splitter obtained from repeating another splitter."""
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+
+__author__ = ["fkiraly"]
+
+
+from sktime.split.base import BaseSplitter
+
+
+class Repeat(BaseSplitter):
+    """Add repetitions to a splitter, element-wise or sequence-wise.
+
+    Element-wise means: if the original splitter splits a series s into s1, s2, s3,
+    then a 2-times repeat splits s into s1, s1, s2, s2, s3, s3.
+    Sequence-wise means: if the original splitter splits a series s into s1, s2, s3,
+    then a 2-times repeat splits s into s1, s2, s3, s1, s2, s3.
+
+    This splitter also allows to control whether repetitions are exact
+    or independent pseuo-random, for stochastic splitters.
+
+    Parameters
+    ----------
+    splitter : sktime splitter object, BaseSplitter descendant instance
+        splitter to repeat
+    times : int, default=1
+        number of times to repeat the splitter
+    mode : str, one of "entry" and "sequence", default="entry"
+        mode of repetition
+        "entry" repeats each entry of the split ``times`` times
+        "sequence" repeats the entire sequence of splits ``times`` times
+    random_repeat : bool, default=False
+        whether repetitions should be exact or independent pseudo-random
+        If False, repetitions are exact (default)
+        If True, repetitions are random, ``splitter`` is cloned for each repetition.
+        Note: if a random seed is set in ``splitter``, the effect is the same
+        as setting ``random_repeat`` to False, even if ``random_repeat`` is True.
+    """
+
+    _tags = {
+        "split_hierarchical": True,
+        "split_series_uses": "iloc",
+    }
+
+    def __init__(self, splitter, times=1, mode="entry", random_repeat=False):
+        self.splitter = splitter
+        self.times = times
+        self.mode = mode
+        self.random_repeat = random_repeat
+
+        super().__init__()
+
+        ALLOWED_MODES = ["entry", "sequence"]
+        if mode not in ALLOWED_MODES:
+            raise ValueError(
+                f"Mode in Repeat splitter should be one of {ALLOWED_MODES}, "
+                f"but found {mode}"
+            )
+
+        tags_to_clone = ["split_series_uses"]
+
+        self.clone_tags(splitter, tags_to_clone)
+
+    def _split(self, y):
+        """Get iloc references to train/test splits of `y`.
+
+        private _split containing the core logic, called from split
+
+        Parameters
+        ----------
+        y : pd.Index
+            Index of time series to split
+
+        Yields
+        ------
+        train : 1D np.ndarray of dtype int
+            Training window indices, iloc references to training indices in y
+        test : 1D np.ndarray of dtype int
+            Test window indices, iloc references to test indices in y
+        """
+        yield from self._repeat(y, method="split")
+
+    def _split_loc(self, y):
+        """Get loc references to train/test splits of `y`.
+
+        private _split containing the core logic, called from split_loc
+
+        Default implements using split and y.index to look up the loc indices.
+        Can be overridden for faster implementation.
+
+        Parameters
+        ----------
+        y : pd.Index
+            index of time series to split
+
+        Yields
+        ------
+        train : pd.Index
+            Training window indices, loc references to training indices in y
+        test : pd.Index
+            Test window indices, loc references to test indices in y
+        """
+        yield from self._repeat(y, method="split_loc")
+
+    def _repeat(self, y, method="split"):
+        """Repeat the splitter.
+
+        Parameters
+        ----------
+        method : str, one of "split" and "split_loc", default="split"
+            method to repeat
+
+        Yields
+        ------
+        train : 1D np.ndarray of dtype int or pd.Index
+            Training window indices, iloc or loc references to training indices in y
+        test : 1D np.ndarray of dtype int or pd.Index
+            Test window indices, iloc or loc references to test indices in y
+        """
+        random_repeat = self.random_repeat
+
+        if random_repeat:
+            spl_clones = [self.splitter.clone() for _ in range(self.times)]
+            spl_gens = [getattr(spl, method)(y) for spl in spl_clones]
+        else:
+            one_clone = self.splitter.clone()
+            one_gen = getattr(one_clone, method)(y)
+
+        if self.mode == "entry" and not random_repeat:
+            for train, test in one_gen:
+                for _ in range(self.times):
+                    yield train, test
+        elif self.mode == "entry" and random_repeat:
+            for _ in range(self.times):
+                for spl_gen in spl_gens:
+                    yield next(spl_gen)
+        elif self.mode == "sequence" and not random_repeat:
+            all_res = list(one_gen)
+            for _ in range(self.times):
+                for train, test in all_res:
+                    yield train, test
+        elif self.mode == "sequence" and random_repeat:
+            for spl_gen in spl_gens:
+                yield from spl_gen
+
+    def get_n_splits(self, y) -> int:
+        """Return the number of splits.
+
+        Parameters
+        ----------
+        y : pd.Series or pd.Index, optional (default=None)
+            Time series to split
+
+        Returns
+        -------
+        n_splits : int
+            The number of splits.
+        """
+        return self._splitter.get_n_splits(y) * self.times
+
+    @classmethod
+    def get_test_params(cls, parameter_set="default"):
+        """Return testing parameter settings for the splitter.
+
+        Parameters
+        ----------
+        parameter_set : str, default="default"
+            Name of the set of test parameters to return, for use in tests. If no
+            special parameters are defined for a value, will return `"default"` set.
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class
+            Each dict are parameters to construct an "interesting" test instance, i.e.,
+            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
+            `create_test_instance` uses the first (or only) dictionary in `params`
+        """
+        from sktime.split import ExpandingWindowSplitter
+
+        modes = ["entry", "sequence"]
+        repeats = [True, False]
+        timess = [2, 3, 7]
+
+        params = []
+        for mode in modes:
+            for random_repeat in repeats:
+                for times in timess:
+                    params.append(
+                        {
+                            "mode": mode,
+                            "random_repeat": random_repeat,
+                            "times": times,
+                            "splitter": ExpandingWindowSplitter(),
+                        }
+                    )
+
+        return params
diff --git a/sktime/transformations/panel/catch22.py b/sktime/transformations/panel/catch22.py
index 469c4bba7..2339a9f60 100644
--- a/sktime/transformations/panel/catch22.py
+++ b/sktime/transformations/panel/catch22.py
@@ -328,7 +328,7 @@ class Catch22(BaseTransformer):
 
         f_count = -1
         for i in range(len(X)):
-            series = np.array(X[i])
+            series = np.array(X.iloc[i])
             dim = i * len(f_idx)
             outlier_series = None
             smin = None
diff --git a/sktime/transformations/series/dilation_mapping.py b/sktime/transformations/series/dilation_mapping.py
index e8dc1aa74..967db827b 100644
--- a/sktime/transformations/series/dilation_mapping.py
+++ b/sktime/transformations/series/dilation_mapping.py
@@ -105,9 +105,9 @@ class DilationMappingTransformer(BaseTransformer):
         return self._dilate_series(X, self.dilation)
 
     def _dilate_series(self, x, d):
-        x_dilated = pd.Series(name=x.name)
-        for i in range(0, d):
-            x_dilated = pd.concat((x_dilated, x[i::d]), axis=0)
+        x_dilations = [x[i::d] for i in range(0, d)]
+        x_dilated = pd.concat(x_dilations, axis=0)
+        x_dilated.name = x.name
         return x_dilated.reset_index(drop=True)
 
     @classmethod
diff --git a/sktime/transformations/series/impute.py b/sktime/transformations/series/impute.py
index a04c9845e..efbc4bae5 100644
--- a/sktime/transformations/series/impute.py
+++ b/sktime/transformations/series/impute.py
@@ -26,41 +26,54 @@ class Imputer(BaseTransformer):
         Method to fill the missing values.
 
         * "drift" : drift/trend values by sktime.PolynomialTrendForecaster(degree=1)
-            first, X in transform() is filled with ffill then bfill
-            then PolynomialTrendForecaster(degree=1) is fitted to filled X, and
-            predict values are queried at indices which had missing values
+          first, X in transform() is filled with ffill then bfill
+          then PolynomialTrendForecaster(degree=1) is fitted to filled X, and
+          predict values are queried at indices which had missing values
         * "linear" : linear interpolation, uses pd.Series.interpolate()
             WARNING: This method can not extrapolate, so it is fitted always on the
             data given to transform().
         * "nearest" : use nearest value, uses pd.Series.interpolate()
         * "constant" : same constant value (given in arg value) for all NaN
-        * "mean" : pd.Series.mean() of *fit* data
-        * "median" : pd.Series.median() of *fit* data
-        * "backfill" to "bfill" : adapted from pd.Series.fillna()
-        * "pad" or "ffill" : adapted from pd.Series.fillna()
+        * "mean" : pd.Series.mean() of data seen in ``fit``
+          to use data in transform, wrap this estimator in ``FitInTransform``
+        * "median" : pd.Series.median() of data seen in ``fit``
+          to use data in transform, wrap this estimator in ``FitInTransform``
+        * "backfill" to "bfill" : applies ``pd.Series.bfill`` to all data
+        * "pad" or "ffill" : applies ``pd.Series.ffill`` to all data
         * "random" : random values between pd.Series.min() and .max() of *fit* data
-            if pd.Series dtype is int, sample is uniform discrete
-            if pd.Series dtype is float, sample is uniform continuous
-        * "forecaster" : use an sktime Forecaster, given in param forecaster.
-            First, X in *fit* is filled with ffill then bfill
-            then forecaster is fitted to filled X, and *predict* values are queried
-            at indices of X data in *transform* which had missing values
-        For the following methods, the train data is used to fit them:
-        "drift", "mean", "median", "random". For all other methods, the
-        transform data is sufficient to compute the impute values.
-
-    missing_values : int/float/str, default=None
-        The placeholder for the missing values. All occurrences of
-        missing_values will be imputed, in addition to np.nan.
-        If None, then only np.nan values are imputed.
+          if pd.Series dtype is int, sample is uniform discrete
+          if pd.Series dtype is float, sample is uniform continuous
+        * "forecaster" : use an sktime forecaster, given in param ``forecaster``.
+          First, X seed in ``fit`` is filled with ``ffill`` then ``bfill``
+          then forecaster is fitted to filled X, and ``predict`` values are queried
+          at indices of X data in ``transform`` which had missing values.
+          ``forecaster`` is always applied by variable and instance.
+
+        The following methods, fit non-trivially to the data seen in ``fit``:
+        "drift", "mean", "median", "random". All other methods
+        do not depend on values seen in ``fit``.
+
+    missing_values : str, int, float, regex, list, or None, default=None
+        Value to consider as `np.nan`` and impute, passed to ``DataFrame.replace``
+        If str, int, float, all entries equal to ``missing_values`` will be imputed,
+        in addition to ``np.nan.``
+        If regex, all entrie matching regex will be imputed, in addition to ``np.nan.``
+        If list, must be list of str, int, float, or regex.
+        Values matching any list element by above rules will be imputed,
+        in addition to ``np.nan``.
+        If None, then only ``np.nan`` values are imputed.
+
     value : int/float, default=None
         Value to use to fill missing values when method="constant".
+        Only used if ``method="constant"``, otherwise ignored.
+
     forecaster : Any Forecaster based on sktime.BaseForecaster, default=None
         Use a given Forecaster to impute by insample predictions when
-        method="forecaster". Before fitting, missing data is imputed with
-        method="ffill" or "bfill" as heuristic. in case of multivariate X,
-        the forecaster is applied separate to each column like a
-        ColumnEnsembleForecaster.
+        ``method="forecaster"``. Before fitting, missing data is imputed with
+        ``method="ffill"`` or ``"bfill"`` as heuristic. In case of multivariate X,
+        a clone of ``forecaster`` is applied per column.
+        Only used if ``method="forecaster"``, otherwise ignored.
+
     random_state : int/float/str, optional
         Value to set random.seed() if method="random", default None
 
@@ -275,25 +288,15 @@ class Imputer(BaseTransformer):
             "forecaster",
         ]:
             raise ValueError(f"Given method {method} is not an allowed method.")
-        if (
-            self.value is not None
-            and method != "constant"
-            or method == "constant"
-            and self.value is None
-        ):
+        if method == "constant" and self.value is None:
             raise ValueError(
-                """Imputing with a value can only be
-                used if method="constant" and if parameter "value" is not None"""
+                """Imputing with method=\"constant\" can only be used if parameter
+                value" is not None"""
             )
-        elif (
-            self.forecaster is not None
-            and method != "forecaster"
-            or method == "forecaster"
-            and self.forecaster is None
-        ):
+        elif method == "forecaster" and self.forecaster is None:
             raise ValueError(
-                """Imputing with a forecaster can only be used if
-                method=\"forecaster\" and if arg forecaster is not None"""
+                """Imputing with method=\"forecaster\" can only be
+                used if param forecaster is not None"""
             )
         else:
             pass


Test patch:
diff --git a/sktime/forecasting/base/tests/test_base.py b/sktime/forecasting/base/tests/test_base.py
index 7bf8d2efe..17a22bf02 100644
--- a/sktime/forecasting/base/tests/test_base.py
+++ b/sktime/forecasting/base/tests/test_base.py
@@ -14,6 +14,7 @@ from pandas.testing import assert_series_equal
 from sktime.datatypes import check_is_mtype, convert
 from sktime.datatypes._utilities import get_cutoff, get_window
 from sktime.forecasting.arima import ARIMA
+from sktime.forecasting.compose import YfromX
 from sktime.forecasting.naive import NaiveForecaster
 from sktime.forecasting.theta import ThetaForecaster
 from sktime.forecasting.var import VAR
@@ -455,3 +456,25 @@ def test_range_fh_in_predict():
 
     assert isinstance(var_predictions, pd.DataFrame)
     assert var_predictions.shape == (10 * 2, 5)
+
+
+def test_remember_data():
+    """Test that the ``remember_data`` flag works as expected."""
+    from sktime.datasets import load_airline
+
+    y = load_airline()
+    X = load_airline()
+    f = YfromX.create_test_instance()
+
+    # turn off remembering _X, _y by config
+    f.set_config(**{"remember_data": False})
+    f.fit(y, X, fh=[1, 2, 3])
+
+    assert f._X is None
+    assert f._y is None
+
+    f.set_config(**{"remember_data": True})
+    f.fit(y, X, fh=[1, 2, 3])
+
+    assert f._X is not None
+    assert f._y is not None
diff --git a/sktime/tests/_config.py b/sktime/tests/_config.py
index 7b7783d7d..7a9aabbf5 100644
--- a/sktime/tests/_config.py
+++ b/sktime/tests/_config.py
@@ -117,6 +117,11 @@ EXCLUDED_TESTS = {
     "MLPClassifier": [
         "test_fit_idempotent",
     ],
+    "CNTCClassifier": [
+        "test_fit_idempotent",
+        "test_persistence_via_pickle",
+        "test_save_estimators_to_file",
+    ],
     "InceptionTimeClassifier": [
         "test_fit_idempotent",
     ],
diff --git a/sktime/transformations/series/tests/test_imputer.py b/sktime/transformations/series/tests/test_imputer.py
index d0275454e..05b958d10 100644
--- a/sktime/transformations/series/tests/test_imputer.py
+++ b/sktime/transformations/series/tests/test_imputer.py
@@ -31,6 +31,8 @@ z.iloc[0] = np.nan
 z.iloc[-1] = np.nan
 
 
+@pytest.mark.parametrize("forecaster", [None, NaiveForecaster()])
+@pytest.mark.parametrize("value", [None, 1])
 @pytest.mark.parametrize("Z", [y, X, z])
 @pytest.mark.parametrize(
     "method",
@@ -47,10 +49,10 @@ z.iloc[-1] = np.nan
         "forecaster",
     ],
 )
-def test_imputer(method, Z):
+def test_imputer(method, Z, value, forecaster):
     """Test univariate and multivariate Imputer with all methods."""
-    forecaster = NaiveForecaster() if method == "forecaster" else None
-    value = 3 if method == "constant" else None
+    forecaster = NaiveForecaster() if method == "forecaster" else forecaster
+    value = 1 if method == "constant" else value
     t = Imputer(method=method, forecaster=forecaster, value=value)
     y_hat = t.fit_transform(Z)
     assert not y_hat.isnull().to_numpy().any()