[
  {
    "path": ".gitignore",
    "content": "*~\n*.elc\n\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n\n"
  },
  {
    "path": "README.org",
    "content": "# -*- mode: org; coding:utf-8; -*-\n#+TITLE: Aggregate Values in a Table\n#+OPTIONS: ^:{} authors:Thierry Banel, Michael Brand toc:nil\n\nAggregating a table is creating a new table by computing sums,\naverages, and so on, out of material from the first table.\n\n* New\nTranspose-babel blocks now handle =@#= and =hline= special columns. =@#= is\nthe input table row number. =hline= is the block number between two\nhorizontal lines where the current row is located.\n\nAnd by the way, yes, =orgtbl-aggregate= comes with =orgtbl-transpose= as a\nbonus. It flips rows with columns.\n\n* Table of Contents\n:PROPERTIES:\n:TOC:      :include siblings :depth 2 :force () :ignore (this) :local (nothing)\n:CUSTOM_ID: table-of-contents\n:END:\n\n:CONTENTS:\n- [[#examples][Examples]]\n  - [[#a-very-simple-example][A very simple example]]\n  - [[#demonstrate-sum-and-average-computing][Demonstrate sum and average computing]]\n  - [[#example-without-days][Example without days]]\n  - [[#example-of-counting-each-combination][Example of counting each combination]]\n- [[#stop-reading-here-8020][Stop reading here! 80/20]]\n  - [[#name-your-input-table][Name your input table]]\n  - [[#create-an-aggregation-block][Create an aggregation block]]\n  - [[#refresh-the-aggregation][Refresh the aggregation]]\n- [[#equivalent-in-sql-r-datamash-el-tblfn-awk-c][Equivalent in SQL, R, Datamash, el-tblfn, Awk, C++]]\n  - [[#sql-equivalent][SQL equivalent]]\n  - [[#r-equivalent][R equivalent]]\n  - [[#datamash-equivalent][Datamash equivalent]]\n  - [[#el-tblfn][el-tblfn]]\n  - [[#awk-equivalent][Awk equivalent]]\n  - [[#c-equivalent][C++ equivalent]]\n- [[#wizards][Wizards]]\n  - [[#guiding-traditional-wizard][Guiding (traditional) wizard]]\n  - [[#experimental-free-form-wizard][Experimental free form wizard]]\n- [[#the-cols-parameter][The :cols parameter]]\n  - [[#names-of-input-columns][Names of input columns]]\n  - [[#grouping-specifications-in-cols][Grouping specifications in :cols]]\n  - [[#the-hline-column][The hline column]]\n  - [[#the--column][The @# column]]\n  - [[#aggregation-formulas-in-cols][Aggregation formulas in :cols]]\n  - [[#correlation-of-two-columns][Correlation of two columns]]\n  - [[#almost-any-expression-can-be-specified][(Almost) any expression can be specified]]\n- [[#column-names][Column names]]\n  - [[#input-table-with-or-without-a-header][Input table with or without a header]]\n  - [[#column-names-of-the-input-table][Column names of the input table]]\n  - [[#multiple-lines-header][Multiple lines header]]\n  - [[#custom-column-names][Custom column names]]\n- [[#formatters][Formatters]]\n  - [[#org-mode-compatible-formatters][Org Mode compatible formatters]]\n  - [[#debugging-formatters][Debugging formatters]]\n  - [[#discarding-an-output-column][Discarding an output column]]\n- [[#sorting][Sorting]]\n  - [[#example-with-one-sorting-column][Example with one sorting column]]\n  - [[#several-sorting-columns][Several sorting columns]]\n- [[#hlines-in-the-output-table][hlines in the output table]]\n  - [[#output-hlines-depends-on-sorting-columns][Output hlines depends on sorting columns]]\n  - [[#example-with-hline-2][Example with hline 2]]\n- [[#cells-processing][Cells processing]]\n  - [[#where-calc-interpretation-happens][Where Calc interpretation happens?]]\n  - [[#dates][Dates]]\n  - [[#durations][Durations]]\n  - [[#empty-and-malformed-input-cells][Empty and malformed input cells]]\n  - [[#symbolic-computation][Symbolic computation]]\n  - [[#intervals][Intervals]]\n  - [[#error-or-precision-forms][Error or precision forms]]\n- [[#wide-variety-of-inputs][Wide variety of inputs]]\n  - [[#standard-org-mode-input][Standard Org Mode input]]\n  - [[#virtual-input-table-from-babel][Virtual input table from Babel]]\n  - [[#an-org-id][An Org ID]]\n  - [[#csv-input][CSV input]]\n  - [[#json-input][JSON input]]\n  - [[#input-slicing][Input slicing]]\n  - [[#the-cond-filter][The :cond filter]]\n  - [[#virtual-input-columns][Virtual input columns]]\n- [[#post-processing][Post-processing]]\n  - [[#spreadsheet-formulas][Spreadsheet formulas]]\n  - [[#algorithm-post-processing][Algorithm post processing]]\n  - [[#grand-total][Grand total]]\n  - [[#chaining][Chaining]]\n- [[#pull--push][Pull & Push]]\n  - [[#pull-mode][Pull mode]]\n  - [[#push-mode][Push mode]]\n  - [[#pull-or-push-][Pull or push ?]]\n- [[#debugging][Debugging]]\n  - [[#seeing-the--forms][Seeing the $ forms]]\n  - [[#seeing-calc-formulas-before-evaluation][Seeing Calc formulas before evaluation]]\n  - [[#seeing-lisp-internal-form-of-calc-formulas][Seeing Lisp internal form of Calc formulas]]\n  - [[#example-of-debugging-vsumnn2][Example of debugging vsum(nn^2)]]\n  - [[#summary-of-debugging-formatters][Summary of debugging formatters]]\n- [[#tricks][Tricks]]\n  - [[#sorting-0][Sorting]]\n  - [[#a-few-lowest-or-highest-values][A few lowest or highest values]]\n  - [[#span-of-values][Span of values]]\n  - [[#no-aggregation][No aggregation]]\n- [[#installation][Installation]]\n- [[#authors-contributors][Authors, contributors]]\n- [[#changes][Changes]]\n- [[#gpl-3-license][GPL 3 License]]\n:END:\n\n* Examples\n:PROPERTIES:\n:CUSTOM_ID: examples\n:END:\n\n** A very simple example\n:PROPERTIES:\n:CUSTOM_ID: a-very-simple-example\n:END:\n\nWe have a table of activities and quantities (whatever they are) over\nseveral days.\n\n#+begin_example\n#+name: original\n| Day       | Color | Level | Quantity |\n|-----------+-------+-------+----------|\n| Monday    | Red   |    30 |       11 |\n| Monday    | Blue  |    25 |        3 |\n| Tuesday   | Red   |    51 |       12 |\n| Tuesday   | Red   |    45 |       15 |\n| Tuesday   | Blue  |    33 |       18 |\n| Wednesday | Red   |    27 |       23 |\n| Wednesday | Blue  |    12 |       16 |\n| Wednesday | Blue  |    15 |       15 |\n| Thursday  | Red   |    39 |       24 |\n| Thursday  | Red   |    41 |       29 |\n| Thursday  | Red   |    49 |       30 |\n| Friday    | Blue  |     7 |        5 |\n| Friday    | Blue  |     6 |        8 |\n| Friday    | Blue  |    11 |        9 |\n#+end_example\n\nTo begin with we want to gather all colors and count how many times\nthey appear. We are interested only in the second column named =Color=\n\nFirst we give a name to the table through the =#+NAME:=\nor =#+TBLNAME:= tags, just above the table.\nThen we create a /dynamic block/ to receive the aggregation:\n\n#+begin_example\ndesired output columns╶──────────────────────────╮\nthe input table╶──────────────╮                  │\ntype of processing╶─╮         │                  │\n            ╭───────╯         │                  │\n            ▼             ╭───┴────╮       ╭─────┴───────╮\n#+begin: aggregate :table \"original\" :cols \"Color count()\"\n#+end:\n#+end_example\n\nNow typing =C-c C-c= in the dynamic block counts the colors in the original table:\n\n#+begin_example\nC-c C-c here to refresh╶╮\n             ╭──────────╯\n             ▼\n#+begin: aggregate :table \"original\" :cols \"Color count()\"\n| Color | count() |\n|-------+---------|\n| Red   |       7 |\n| Blue  |       7 |\n#+end:\n#+end_example\n\nOrgAggregate found two colors, =Red= and =Blue=. It found 7 occurrences\nfor each.\n\n** Demonstrate sum and average computing\n:PROPERTIES:\n:CUSTOM_ID: demonstrate-sum-and-average-computing\n:END:\n\nNow we want to aggregate this table for each day (because several rows\nexist for each day). We want the average value of the =Level= column for\neach day, and the sum of the =Quantity= column. We write down the\nblock specifying that (later we will see how to automate the creation\nof such a block with a [[#wizards][wizard]]):\n\n#+begin_example\nsum aggregation╶─────────────────────────────────────────────────╮\naverage aggregation╶───────────────────────────────╮             │\nkey grouping column╶───────────────────────╮       │             │\n                                          ╭┴╮ ╭────┴─────╮ ╭─────┴──────╮\n#+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n#+end\n#+end_example\n\nTyping =C-c C-c= in the dynamic block computes the aggregation:\n\n#+begin_example\nC-c C-c here to refresh╶╮\n             ╭──────────╯\n             ▼\n#+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n                                          ╰┬╯ ╰────┬─────╯ ╰──────┬─────╯\n   ╭───────────────────────────────────────╯       │              │\n   │               ╭───────────────────────────────╯              │\n   │               │               ╭──────────────────────────────╯\n  ╭┴╮         ╭────┴─────╮   ╭─────┴──────╮\n| Day       | vmean(Level) | vsum(Quantity) |\n|-----------+--------------+----------------|\n| Monday    |         27.5 |             14 |\n| Tuesday   |           43 |             45 |\n| Wednesday |           18 |             54 |\n| Thursday  |           43 |             83 |\n| Friday    |            8 |             22 |\n#+end\n#+end_example\n\nThe source table is not changed in any way.\n\nTo get this result, we specified columns in this way, after the\n=:cols= parameter:\n\n- =Day= : we got the same column as in the source table, except\n  entries are not duplicated.  Here =Day= acts as a /key grouping column/.\n  We may specify as many key columns as we want just by naming them.\n  We get only one aggregated row for each different combination\n  of values of key grouping columns.\n\n- =vmean(Level)= : this instructs OrgAggregate to compute the average of\n  values found in the =Level= column, grouped by the same =Day=.\n\n- =vsum(Quantity)=: OrgAggregate computes the sum of values found in the\n  =Quantity= column, one sum for each =Day=.\n\n** Example without days\n:PROPERTIES:\n:CUSTOM_ID: example-without-days\n:END:\n\nMaybe we are just interested in the sum of =Quantities=, regardless of\n=Days=. We just type:\n\n#+begin_example\nC-c C-c here to refresh╶╮\n             ╭──────────╯\n             ▼\n#+begin: aggregate :table \"original\" :cols \"vsum(Quantity)\"\n                                            ╰─────┬──────╯\n       ╭──────────────────────────────────────────╯\n  ╭────┴───────╮\n| vsum(Quantity) |\n|----------------|\n|            218 |\n#+end\n#+end_example\n\n** Example of counting each combination\n:PROPERTIES:\n:CUSTOM_ID: example-of-counting-each-combination\n:END:\n\nwe may want to count the number of rows for each combination of\n=Day= and =Color=:\n\n#+BEGIN_EXAMPLE\nC-c C-c here to refresh╶╮\n             ╭──────────╯\n             ▼\n#+BEGIN: aggregate :table \"original\" :cols \"count() Day Color\"\n                                            ╰──┬──╯ ╰┬╯ ╰─┬─╯\n     ╭─────────────────────────────────────────╯     │    │\n     │       ╭───────────────────────────────────────╯    │\n     │       │            ╭───────────────────────────────╯\n  ╭──┴──╮   ╭┴╮         ╭─┴─╮\n| count() | Day       | Color |\n|---------+-----------+-------|\n|       1 | Monday    | Red   |\n|       1 | Monday    | Blue  |\n|       2 | Tuesday   | Red   |\n|       1 | Tuesday   | Blue  |\n|       1 | Wednesday | Red   |\n|       2 | Wednesday | Blue  |\n|       3 | Thursday  | Red   |\n|       3 | Friday    | Blue  |\n#+END\n#+END_EXAMPLE\n\nIf we want to get measurements for =Colors= rather than =Days=, we\ntype:\n\n#+begin_example\nC-c C-c here to refresh╶╮\n             ╭──────────╯\n             ▼\n#+begin: aggregate :table \"original\" :cols \"Color vmean(Level) vsum(Quantity)\"\n                                            ╰─┬─╯ ╰────┬─────╯ ╰─────┬──────╯\n    ╭─────────────────────────────────────────╯        │             │\n    │           ╭──────────────────────────────────────╯             │\n    │           │               ╭────────────────────────────────────╯\n  ╭─┴─╮    ╭────┴─────╮   ╭─────┴──────╮\n| Color |  vmean(Level) | vsum(Quantity) |\n|-------+---------------+----------------|\n| Red   | 40.2857142857 |            144 |\n| Blue  | 15.5714285714 |             74 |\n#+end\n#+end_example\n\n* Stop reading here! 80/20\n:PROPERTIES:\n:CUSTOM_ID: stop-reading-here-8020\n:END:\n\nIf you managed to get here, you are at 80/20 (thanks Pareto!). You\ngrasped only 20% of the OrgAggregate features, but those 20% cover 80%\nof the use cases.\n\nTo summarize the 20%:\n\n** Name your input table\n:PROPERTIES:\n:CUSTOM_ID: name-your-input-table\n:END:\n- Select one of your Org table, and be ready to aggregate values\n  from it right in the same file.\n\n- Give a name to your table with a special line just above it.\n\n#+begin_example\nname here╶───╮\n        ╭────╯\n        ▼\n#+name: original\n| Day       | Color | Level | Quantity |\n|-----------+-------+-------+----------|\n| Monday    | Red   |    30 |       11 |\n| Monday    | Blue  |    25 |        3 |\n…\n#+end_example\n\n** Create an aggregation block\n:PROPERTIES:\n:CUSTOM_ID: create-an-aggregation-block\n:END:\n\n#+begin_example\n#+begin: aggregate\n#+end:\n#+end_example\n\n- *Input:* specify a =:table= parameter.\n- *Output:* specify the desired output columns with the =:cols= parameter.\n\n#+begin_example\noutput╶───────────────────────────────────────────╮\ninput╶───────────────────────╮                    │\n                             │                    │\n                          ╭──┴───╮       ╭────────┴─────────╮\n#+begin: aggregate :table original :cols \"Day vsum(Quantity)\"\n#+end:\n#+end_example\n\n** Refresh the aggregation\n:PROPERTIES:\n:CUSTOM_ID: refresh-the-aggregation\n:END:\n\n- Type =C-c C-c= on the =#+begin:= line now and whenever you want to\n  refresh the aggregation.\n\n#+begin_example\nC-c C-c here╶─╮\n              │\n              ▼\n#+begin: aggregate :table original :cols \"Day vsum(Quantity)\"\n| Day       | vsum(Quantity) |\n|-----------+----------------|\n| Monday    |             14 |\n| Tuesday   |             45 |\n…\n#+end:\n#+end_example\n\n* Equivalent in SQL, R, Datamash, el-tblfn, Awk, C++\n:PROPERTIES:\n:CUSTOM_ID: equivalent-in-sql-r-datamash-el-tblfn-awk-c\n:END:\n\nAggregation is a widely used method to get insights in tabular\ndata. Use whatever environment best suits your needs.\n\nOrgAggregate is great when you want to output and Org Mode\ntable. Also, OrgAggregate has no dependency other than Emacs, not even\nother Lisp packages.\n\n** SQL equivalent\n:PROPERTIES:\n:CUSTOM_ID: sql-equivalent\n:END:\n\nIf you are familiar with SQL, you would get a similar result with the\n=GROUP BY= statement:\n\n#+begin_src sql\nselect Day, mean(Level), sum(Quantity)\nfrom original\ngroup by Day;\n#+end_src\n\n** R equivalent\n:PROPERTIES:\n:CUSTOM_ID: r-equivalent\n:END:\n\nIf you are familiar with the R statistical language, you would get a\nsimilar result with =factor= and =aggregate= functions:\n\n#+begin_src R\noriginal <- the table as a data.frame\nday_factor <- factor(original$Day)\naggregate (original$Level   , list(Day=day_factor), mean)\naggregate (original$Quantity, list(Day=day_factor), sum )\n#+end_src\n\n** Datamash equivalent\n:PROPERTIES:\n:CUSTOM_ID: datamash-equivalent\n:END:\n\nThe command-line Datamash software operates on CSV files and can\nachieve a similar result:\n\n#+begin_src shell\ndatamash -H -g Day mean Level sum Quantity <original.csv\nGroupBy(Day)  mean(Level)  sum(Quantity)\nMonday        27.5         14\nTuesday       43           45\nWednesday     18           54\nThursday      43           83\nFriday         8           22\n#+end_src\n\n** el-tblfn\n:PROPERTIES:\n:CUSTOM_ID: el-tblfn\n:END:\nThe Misohena's el-tblfn package aggregates Org Mode tables through\nLisp scripts. See https://github.com/misohena/el-tblfn. Example:\n\n#+begin_example\n,#+begin_src elisp :var original=original :colnames no :hlines yes\n(require 'tblfn)\n(thread-first\n  (tblfn-aggregate original \"Quantity\" \"Total\"))\n,#+end_src\n#+end_example\n\nIt is not clear how el-tblfn can compute the =mean= (or other\naggregating functions) on the =Level= column. Would the author want to\ncomplete the example?\n\n** Awk equivalent\n:PROPERTIES:\n:CUSTOM_ID: awk-equivalent\n:END:\nAwk is a line-oriented filter which has been arround in Unix for\ndecades. Here we use the standard Org Mode support for Awk.\n\n#+begin_example\n#+begin_src awk :stdin original\nNR>1 {\n     Day         =$1\n     Color       =$2\n     Level       =$3\n     Quantity    =$4\n     SumLevel[Day]    += Level\n     SumQuantity[Day] += Quantity\n     Count[Day]       ++\n}\nEND {\n    for (d in SumQuantity) {\n        printf \"%s %s %s\\n\", d, SumLevel[d]/Count[d], SumQuantity[d]\n    }\n}\n#+end_src\n#+end_example\n\n** C++ equivalent\n:PROPERTIES:\n:CUSTOM_ID: c-equivalent\n:END:\nC++ has hash-maps in its standard template library. And Org Mode\nprovides support for C++ Babel blocks. Thus, it is quite\nstraigthforward to aggegrate in this language.\n\n(Don't forget to customize =org-src-lang-modes= to activate C++ support\nin Org Mode).\n\n#+begin_example\n#+begin_src C++ :var original=original :includes '(<iostream> <unordered_map>)\nusing namespace std;\nunordered_map<string,double> SumLevel;\nunordered_map<string,double> SumQuantity;\nunordered_map<string,double> Count;\nfor (auto row : original) {\n  auto Day      = row[0];\n  auto Color    = row[1];\n  auto Level    = stod(row[2]);\n  auto Quantity = stod(row[3]);\n  SumLevel[Day]    += Level;\n  SumQuantity[Day] += Quantity;\n  Count[Day]       ++;\n}\nfor (auto it : SumQuantity)\n  cout<<it.first<<\" \"\n      <<SumLevel[it.first]/Count[it.first]<<\" \"\n      <<SumQuantity[it.first]<<\"\\n\";\n#+end_src\n#+end_example\n\n* Wizards\n:PROPERTIES:\n:CUSTOM_ID: wizards\n:END:\n\nThere are now 2 wizards. Use whichever is easier for you.\n\n** Guiding (traditional) wizard\n:PROPERTIES:\n:CUSTOM_ID: guiding-traditional-wizard\n:END:\n\nType =C-c C-x x= to launch a wizard for creating new dynamic blocks, or\namending existing ones.  Then answer =aggregate= for the type of block,\nand follow the instructions.  (There are several other /dynamic blocks/\nthat can be built this way: =columnview=, =clocktable=, =propview=, =invoice=,\n=transpose=, and any future block).\n\nThere are two modes:\n- basic, =C-c C-x x=, =M-x orgtbl-aggregate-insert-dblock-aggregate=\n- expert,  =C-u C-c C-x x=,  =C-u M-x orgtbl-aggregate-insert-dblock-aggregate=\n\nThe basic mode only queries for an input table in the current buffer\nand the output columns. Of course, if the wizard updates an existing\nblock with exotic parameters, those parameters will be queried even in\nbasic mode.\n\nThe expert mode queries every parameter.\n\nThe wizard has been enhanced & extended.\n\nIt can update an existing block. Put the cursor on a line beginning\nwith =#+begin: aggregate …= and type =C-c C-x x=. The wizard will use\nvalues picked from this line as defaults for user queries.\n\nThe wizard now takes into account the multiple forms of input\ntables. Tables may lie in distant files as well as in the current\nbuffer. They may be computed on the fly by Babel blocks as well as\nregular Org Mode tables. They may also be pointed to by Org IDs.\n\nRegular Org Mode table slicing is now recognized.\n\nPre & post processors are now handled by the wizard.\n\nThe =:hline= parameter is taken into account.\n\nMost of the wizard's queries may be answered by just hitting\nRETURN. This is because many parameters are optional. And because when\namending an existing block, the wizard suggests the old values as the\ndefault answers.\n\nWhen invoking the wizard, a small window opens to display help\nrelative to each field the wizard queries. This help window is in Org\nMode for an easy reading. The window will be closed when the wizard is\ndone or when aborting.\n\n** Experimental free form wizard\n:PROPERTIES:\n:CUSTOM_ID: experimental-free-form-wizard\n:END:\n\nSuppose we have an aggregate block like this one:\n\n#+begin_example\nthe 2 main parameters╶────────╮────────────────────╮\n                              │                    │\n                          ╭───┴────╮       ╭───────┴───────╮\n#+begin: aggregate :table \"my_table\" :cols \"vsum(b) count()\" :hline \"1\"\n#+end:\n#+end_example\n\nThen, hitting the =TAB= key anywhere on the =#+begin:= line (but not at the\nbeginning) unfolds it. The result is this one:\n\n#+begin_example\n#+begin: aggregate                   ╭─╴the 2 main parameters\n#+aggregate: :file                   │\n#+aggregate: :name my_table        ◀─╯\n#+aggregate: :orgid                  │\n#+aggregate: :params                 │\n#+aggregate: :slice                  │\n#+aggregate: :precompute             │\n#+aggregate: :cols vsum(b) count() ◀─╯\n#+aggregate: :cond\n#+aggregate: :hline 1\n#+aggregate: :post\n#+end:\n#+end_example\n\nThis unfolded form is easier to read. Furthermore, help and completion\nis available on each line, by hitting the =TAB= key.\n\nHit =TAB= again on the =#+begin:= line (not at the beginning) to fold back\nthe form. Then hit =C-c C-c= as usual to refresh the aggregation.\n\nThe usual behaviour of Org Mode =TAB= is preserved when the =TAB= key is\npressed at the beginning of line.\n\nAll fields recognized by OrgAggregate are displayed, even when they\nare empty. The empty fields will be ignored when folding back. This\nallows to grasp all the parameters in a single eyesight.\n\nAll in all, this new wizard covers the same features as the\ntraditional, guiding wizard. Except, one can fill the fields in any\norder, and just ignore the unnecessary ones.\n\n* The :cols parameter\n:PROPERTIES:\n:CUSTOM_ID: the-cols-parameter\n:END:\n\nThe =:cols= parameter lists the columns of the resulting table. It\ncontains in any order, grouping key columns and aggregation\nformulas. The choosen order will be reflected in the output table.\n\n** Names of input columns\n:PROPERTIES:\n:CUSTOM_ID: names-of-input-columns\n:END:\n\nThe names of the columns in the original table may be:\n- the names as they appear in the header of the source table,\n- or =$1=, =$2=, =$3= and so on (as in spreadsheet formulas),\n- additionally, the special column =hline= is used to group\n  parts of the source table separated by horizontal lines.\n- the special =@#= column, not in the input table, is the row number.\n\nThe =:cols= parameter may be a string or a list of strings. Examples:\n\n#+begin_example\n  :cols \"Day vmean(Level);f3 vsum(Quantity);f2\"\n  :cols (\"Day\" \"vmean(Level);f3\" \"vsum(Quantity);f2\")\n#+end_example\n\nIf a single string is used, it is split by spaces. Thus, a given\nformula, including its semicolon and modifiers, must not contain any\nspace. If spaces are required within a formula, then use the\nparenthesis list. If a column name has spaces, quote it like this:\n\n#+begin_example\n  'yellow submarine'\n#+end_example\n\n** Grouping specifications in :cols\n:PROPERTIES:\n:CUSTOM_ID: grouping-specifications-in-cols\n:END:\n\nGrouping is done on columns of the source table acting as key columns.\nJust name the key columns.\n\nAdditionally, the =hline= specification means that rows between two\nhorizontal lines should be grouped.\n\nKey columns and =hline= are used to group rows of the source\ntable with unique combinations of those columns.\n\n** The hline column\n:PROPERTIES:\n:CUSTOM_ID: the-hline-column\n:END:\n\nhline = \"horizontal line\"\n\nThe special column named =hline= gives the block number in the source\ntable. A block is the rows separated by horizontal lines in the input\ntable. The first block is numbered =0=. It is a virtual column in the\nsense that it is not a column in the input table. Other than that, it\nis usable as any real column.\n\nHere is a source table containing 3 blocks separated by horizontal\nlines:\n\n#+begin_example\n#+name: originalhl\n| Color | Level | Quantity |\n|-------+-------+----------| ╶╮\n| Red   |    30 |       11 |  │\n| Blue  |    25 |        3 |  ├─first block, n° 0\n| Red   |    51 |       12 |  │\n| Red   |    45 |       15 |  │\n| Blue  |    33 |       18 | ╶╯\n|-------+-------+----------| ╶╮\n| Red   |    27 |       23 |  │\n| Blue  |    12 |       16 |  ├─second block, n° 1\n| Blue  |    15 |       15 |  │\n| Red   |    39 |       24 |  │\n| Red   |    41 |       29 | ╶╯\n|-------+-------+----------| ╶╮\n| Red   |    49 |       30 |  │\n| Blue  |     7 |        5 |  ├─third block, n° 2\n| Blue  |     6 |        8 |  │\n| Blue  |    11 |        9 | ╶╯\n#+end_example\n\nAnd here is the aggregation by those 3 blocks:\n\n#+begin_example\n#+BEGIN: aggregate :table originalhl :cols \"hline vmean(Level) vsum(Quantity)\"\n| hline | vmean(Level) | vsum(Quantity) |\n|-------+--------------+----------------|\n|     0 |         36.8 |             59 | ◀─╴first block , n° 0\n|     1 |         26.8 |            107 | ◀─╴second block, n° 1\n|     2 |        18.25 |             52 | ◀─╴third block , n° 2\n#+END:\n#+end_example\n\nIf we want additional details with the =Color= column, we just name it:\n\n#+begin_example\n#+begin: aggregate :table originalhl :cols \"hline Color vmean(Level) vsum(Quantity)\"\n| hline | Color |  vmean(Level) | vsum(Quantity) |\n|-------+-------+---------------+----------------|\n|     0 | Red   |            42 |             38 | ╶┬──╴first block , n° 0\n|     0 | Blue  |            29 |             21 | ╶╯\n|     1 | Red   | 35.6666666667 |             76 | ╶┬──╴second block, n° 1\n|     1 | Blue  |          13.5 |             31 | ╶╯\n|     2 | Red   |            49 |             30 | ╶┬──╴third block , n° 2\n|     2 | Blue  |             8 |             22 | ╶╯\n#+end:\n#+end_example\n\nThere is an ugly value, =35.6666666667=, in the middle of the\ntable. See later how to format it ([[#org-mode-compatible-formatters][Org Mode compatible formatters]]).\n\n** The @# column\n:PROPERTIES:\n:CUSTOM_ID: the--column\n:END:\n\nThe special column named =@#= gives the row number in the source\ntable. The =@#= name is the same as in the Org table spreadsheet\nformulas. It is a virtual column in the sense that it does not appear\nin the input table. Other than that, it is usable as any real column.\n\nExample: we compute the average of the row number for colors =Red= &\n=Blue=. This gives a sense of whether each colors appears rather at the\nbeginning or the end of the table.\n\n#+begin_example\n#+begin: aggregate :table \"original\" :cols \"Color vmean(@#)\"\n| Color | vmean(@#) |\n|-------+-----------|\n| Red   | 6.2857143 |\n| Blue  | 8.7142857 |\n#+end:\n#+end_example\n\nExample: we recover the input table in reverse order. We use =@#= as a\nkey column sorted in decreasing order with =;^N=. Then we make it\ninvisible with =;<>= so that it does not appear in the output.\n\n#+begin_example\n                        invisible╶─────────────────────────────────────────╮\n                        sorted numerically decreasing╶───────────────────╮ │\n                        row numbers of input table╶──────────────────╮   │ │\n                                                                     │   │ │\n                                                                     ▼   ▼ ▼\n#+begin: aggregate :table \"original\" :cols \"Day Color Level Quantity @#;^N;<>\"\n| Day       | Color | Level | Quantity |\n|-----------+-------+-------+----------|\n| Friday    | Blue  |    11 |        9 |\n| Friday    | Blue  |     6 |        8 |\n| Friday    | Blue  |     7 |        5 |\n| Thursday  | Red   |    49 |       30 |\n| Thursday  | Red   |    41 |       29 |\n| Thursday  | Red   |    39 |       24 |\n| Wednesday | Blue  |    15 |       15 |\n| Wednesday | Blue  |    12 |       16 |\n| Wednesday | Red   |    27 |       23 |\n| Tuesday   | Blue  |    33 |       18 |\n| Tuesday   | Red   |    45 |       15 |\n| Tuesday   | Red   |    51 |       12 |\n| Monday    | Blue  |    25 |        3 |\n| Monday    | Red   |    30 |       11 |\n#+end:\n#+end_example\n\n** Aggregation formulas in :cols\n:PROPERTIES:\n:CUSTOM_ID: aggregation-formulas-in-cols\n:END:\n\nAggregation formulas are applied for each of the groupings, on the\nspecified columns.\n\nWe saw examples with =sum=, =mean=, =count= aggregations. There are\nmany other aggregations. They are based on functions provided by Calc\n(Calc is the powerful Emacs calculator):\n\n- =count()= or =vcount()=\n  + in Calc: =`u #' (`calc-vector-count') [`vcount'])=\n  + gives the number of elements in the group being aggregated;\n    this function may or may not take a column parameter;\n    with a parameter, empty cells are not counted\n    (except with the =E= modifier)..\n\n- =sum(X)= or =vsum(X)=\n  + in Calc: =`u +' (`calc-vector-sum') [`vsum']=\n  + computes the sum of elements being aggregated\n\n- =cnorm(X)=\n  + in Calc: =`v N' (calc-cnorm') [`cnorm']=\n  + like =vsum(X)=, compute the sum of values, but first replacing negative\n    values by their opposite\n\n- =max(X)= or =vmax(X)=\n  + in Calc: =`u X' (`calc-vector-max') [`vmax']=\n  + gives the largest of the elements being aggregated\n\n- =min(X)= or =vmin(X)=\n  + in Calc: =`u N' (`calc-vector-min') [`vmin']=\n  + gives the smallest of the elements being aggregated\n\n- =span(X)= or =vspan(X)=\n  + in Calc: =`v :' (`calc-set-span') [`vspan']=\n  + summarizes values to be aggregated into an interval =[MIN..MAX]=\n    where =MIN= and =MAX= are the minimal and maximal values to be aggregated\n\n- =rnorm(X)=\n  + in Calc: =`v n' (`calc-rnorm) [`rnorm']=\n  + like =vmax(X)=, gives the maximum of values, but first replacing negative\n    values by their opposite\n\n- =mean(X)= or =vmean(X)=\n  + in Calc: =`u M' (`calc-vector-mean') [`vmean']=\n  + computes the average (arithmetic mean) of elements being aggregated\n\n- =meane(X)= or =vmeane(X)=\n  + in Calc: =`I u M' (`calc-vector-mean-error') [`vmeane']=\n  + computes the average (as mean) along with the estimated error of elements being aggregated\n\n- =median(X)= or =vmedian(X)=\n  + in Calc: =`H u M' (`calc-vector-median') [`vmedian']=\n  + computes the median of elements being aggregated, by taking the middle element after sorting them\n\n- =hmean(X)= or =vhmean(X)=\n  + in Calc: =`H I u M' (`calc-vector-harmonic-mean') [`vhmean']=\n  + computes the harmonic mean of elements being aggregated\n\n- =gmean(X)= or =vgmean(X)=\n  + in Calc: =`u G' (`calc-vector-geometric-mean') [`vgmean']=\n  + computes the geometric mean of elements being aggregated\n\n- =sdev(X)= or =vsdev(X)=\n  + in Calc: =`u S' (`calc-vector-sdev') [`vsdev']=\n  + computes the standard deviation of elements being aggregated\n\n- =psdev(X)= or =vpsdev(X)=\n  + in Calc: =`I u S' (`calc-vector-pop-sdev') [`vpsdev']=\n  + computes the population standard deviation (divide by N instead of N-1)\n\n- =var(X)= or =vvar(X)=\n  + in Calc: =`H u S' (`calc-vector-variance') [`vvar']=\n  + computes the variance of elements being aggregated\n\n- =pvar(X)= or =vpvar(X)=\n  + in Calc: =`H u S' (`calc-vector-variance') [`vpvar']=\n  + computes the population variance of elements being aggregated\n\n- =pcov(X,Y)= or =vpcov(X,Y)=\n  + in Calc: =`I u C' (`calc-vector-pop-covariance') [`vpcov']=\n  + computes the population covariance of elements being aggregated from two columns (divides by N)\n\n- =cov(X,Y)= or =vcov(X,Y)=\n  + in Calc: =`u C' (`calc-vector-covariance') [`vcov']=\n  + computes the sample covariance of elements being aggregated from two columns (divides by N-1)\n\n- =corr(X,Y)= or =vcorr(X,Y)=\n  + in Calc: =`H u C' (`calc-vector-correlation') [`vcorr']=\n  + computes the linear correlation coefficient of elements being aggregated in two columns\n\n- =prod(X)= or =vprod(X)=\n  + in Calc: =`u *' (`calc-vector-product') [`vprod']=\n  + computes the product of elements being aggregated\n\n- =vlist(X)= or =list(X)=\n  + gives the list of =X= being aggregated, verbatim, without aggregation.\n\n- =(X)= or =X= in a formula\n  + returns the list of =X= being aggregated, without aggregation,\n    passed through Calc interpretation.\n\n- =sort(X)=\n  + in Calc: =`v S' (`calc-sort') [`sort']=\n  + sorts elements to be aggregated in ascending order;\n    only works on numerical values\n\n- =rsort(X)=\n  + in Calc: =`I v S' (`calc-sort') [`sort']=\n  + sorts elements to be aggregated in descending order;\n    only works on numerical values\n\n- =rev(X)=\n  + in Calc: =`' (`calc-reverse-vector') [`rev']=\n  + returns the list of values to be aggregated in reverse order\n\n- =subvec(X,from)=, =subvec(X,from,to)=\n  + in Calc: =`v s' (`calcFunc-subvec') [`subvec']=\n  + extracts a sub-list from =X= starting at =from= and ending at =to= excluded\n    (or up to the end if =to= is not given).\n    The first value is numbered =1=. So for instance\n    =subvec(X,1,3)= extracts the first two values\n\n- =vmask(M,X)=\n  + in Calc: =`v m' (`calcFunc-vmask') [`vmask']=\n  + extracts a sub-list from =X=, keeping only values for which corresponding values in\n    =M= (the mask) are not zero\n\n- =head(X)=\n  + in Calc: =`v h' (`calc-head') [`head']=\n  + returns the first value to be aggregated\n\n- =rtail(X)=\n  + in Calc: =`H I v h' (`calc-head') [`rtail']=\n  + returns the last value to be aggregated\n\n- =find(X,val)=\n  + in Calc: =`v f' (`calc-vector-find') [`find']=\n  + returns the index of =val= in the list of values to be aggregated, or =0=\n    if =val= is not found. Index starts from =1=\n\n- =rdup(X)=\n  + in Calc: =`v +' (`calc-remove-duplicates') [`rdup']=\n  + remove duplicates from =X= and returns remaining values sorted in\n    ascending order\n\n- =grade(X)=\n  + in Calc: =`v G' (`calc-grade') [`grade']=\n  + returns a list of index of values to be aggregated: the index of the lowest value,\n    then the second lowest value, and so on up to the index of the highest value.\n    Indexes start from =1=\n\n- =rgrade(X)=\n  + in Calc: =`I v G' (`calc-grade') [`rgrade']=\n  + Like =grade= in reverse order\n\nThe aggregation functions may be written with or without a leading\n=v=. =sum= and =vsum= are equivalent.  The =v= form should be\npreferred, as it is the one used in the Org table spreadsheet, and in\nCalc.  The non-v names may be dropped in the future.\n\n** Correlation of two columns\n:PROPERTIES:\n:CUSTOM_ID: correlation-of-two-columns\n:END:\n\nSome aggregations work on two columns (rather than one column for\n=vsum()=, =vmean()=).\nThose aggregations are =vcov(,)=, =vpcov(,)=, =vcorr(,)=.\n- =vcorr(,)= computes the linear correlation between two columns.\n- =vcov(,)= and =vpcov(,)= compute the covariance of two columns.\n\nExample. We create a table where column =y= is a noisy version of\ncolumn =x=:\n\n#+begin_example\n#+tblname: noisydata\n| bin   |  x |       y |\n|-------+----+---------|\n| small |  1 |  10.454 |\n| small |  2 |  21.856 |\n| small |  3 |  30.678 |\n| small |  4 |  41.392 |\n| small |  5 |  51.554 |\n| large |  6 |  61.824 |\n| large |  7 |  71.538 |\n| large |  8 |  80.476 |\n| large |  9 |  90.066 |\n| large | 10 | 101.070 |\n| large | 11 | 111.748 |\n| large | 12 | 121.084 |\n#+tblfm: $3=$2*10+random(1000)/500;%.3f\n#+end_example\n\n#+begin_example\n#+BEGIN: aggregate :table noisydata :cols \"bin vcorr(x,y) vcov(x,y) vpcov(x,y)\"\n| bin   |     vcorr(x,y) |     vcov(x,y) |    vpcov(x,y) |\n|-------+----------------+---------------+---------------|\n| small | 0.999459736649 |        25.434 |       20.3472 |\n| large | 0.999542438688 | 46.4656666667 | 39.8277142857 |\n#+END\n#+end_example\n\nWe see that the correlation between =x= and =y= is very close to =1=,\nmeaning that both columns are correlated. Indeed they are, as the =y=\nis computed from =x= with the formula\n#+begin_example\ny = 10*x + noise_between_0_and_2\n#+end_example\n\n** (Almost) any expression can be specified\n:PROPERTIES:\n:CUSTOM_ID: almost-any-expression-can-be-specified\n:END:\n\nVirtually any Calc formula can be specified as an aggregation formula.\n\nSingle column name (as they appear in the header of the source table,\nor in the form of =$1=, =$2=, ..., or the virtual columns =hline= and\n=@#=) are key columns.  Everything else is given to Calc, to be\ncomputed as an aggregation.\n\nFor instance:\n#+begin_example\n(3)                        ;; a constant\nvmean(2*X+1)               ;; aggregate an expression\nexp(vmean(map(log,N)))     ;; the exponential average\nvsum((X-vmean(X))^2)       ;; X-vmean(X) centers the sample on zero\n#+end_example\n\nArguably, the first expression is useless, but legal.  The aggregation\ncan be applied to a computed list of values.  The result of an\naggregation can be further processed in a formula.  An aggregation can\neven be applied to an expression containing another aggregation.\n\nIn an expression, if a variable has the name of a column, then it is\nreplaced by a Calc vector containing values from this column.\n\nThe special expression =(C)= (a column name within parenthesis)\nyields a list of values to be aggregated from this column, except they\nare not aggregated. Note that parenthesis are required, otherwise, =C=\nwould act as a key grouping column.\n\n* Column names\n:PROPERTIES:\n:CUSTOM_ID: column-names\n:END:\n\n** Input table with or without a header\n:PROPERTIES:\n:CUSTOM_ID: input-table-with-or-without-a-header\n:END:\nThe header of a table gives names to its columns. It is separated from\ndata with an horizontal line.\n\n#+begin_example\ncolumn name is \"quantity\" or \"$2\"╶╮\ncolumn name is \"day\" or \"$1\"╶╮    │\n   ╭─────────────────────────╯    │\n   │              ╭───────────────╯\n   ▼              ▼\n| day       | quantity |\n|-----------+----------|\n| monday    |     12.3 |\n| monday    |      5.9 |\n| thursday  |     41.1 |\n| wednesday |     16.8 |\n#+end_example\n\nIn this example, the input columns may be referred to as =day= and\n=quantity=.\n\nTables without a header are handled by OrgAggregate with /\"dollar\nnames\"/. Example of a table without a header:\n\n#+begin_example\ncolumn name is \"$2\"╶──╮\ncolumn name is \"$1\"╶╮ │\n    ╭───────────────╯ │\n    │               ╭─╯\n    ▼               ▼\n| monday    |     12.3 |\n| monday    |      5.9 |\n| thursday  |     41.1 |\n| wednesday |     16.8 |\n#+end_example\n\nThen columns may be refereed to as =$1= and =$2=.\n\n** Column names of the input table\n:PROPERTIES:\n:CUSTOM_ID: column-names-of-the-input-table\n:END:\n\nColumn names are not necessarily alphanumeric words. They may contain\nany characters, including spaces, quotes, +, -, whatever. They must\nnot extend on several lines thought.\n\nThose names need to be protected with quotes (single or double quotes)\nwithin formulas.\n\nExamples:\n- =:cols= \"=mean('estimated value')=\"\n- =:cond (equal \"true color\" \"Red\")=\n\nQuoting is not required for\n- ASCII letters\n- numbers\n- underscore _, dollar $, dot .\n- accented letters like à é\n- Greek letters like α, Ω\n- northern letters like ø\n- Russian letters like й\n- Esperanto letters like ŭ\n- Japanese ideograms like 量\n\nNote that in =:cond= Lisp expression, only double quotes work. This is\nbecause single quotes in Lisp have a very special meaning.\n\n=Ubuntu Mono= font can be used for displaying aligned Japanese\ncharacters, although not perfectly.\n\n** Multiple lines header\n:PROPERTIES:\n:CUSTOM_ID: multiple-lines-header\n:END:\nThe header of the source table may be more than one row tall.  Only the\nfirst header row is used to match column names between the source\ntable and the =:cols= specifications.\n\nBest effort is made to propagate additional header rows to the\naggregated table.  This happens when the aggregated column refers to a\nsingle source column, either as a key column or a formula involving a\nsingle column.\n\n#+begin_example\n#+name: tall-header\n\n               ╭──────────────────────────────────────────────╮\n           ╭───┴──╮                                           │\n| color  | quantity |  level | ╶╮                             │\n| <l>    |     <r7> |    <3> |  ├─╴header is 3 rows tall      │\n| kolor  |     kiom | nivelo | ╶╯                             │\n|--------+----------+--------|                    ╭───────────╯───────────────╮\n| yellow |       72 |      3 |                    │ only the first header row │\n| green  |       55 |      5 |                    │ is used in formulas       │\n| <c>    |          |        |                    ╰───────────╭───────────────╯\n| orange |       80 |      2 |                                │\n| yellow |       13 |      1 |                                │\n                                                          ╭───┴──╮\n#+BEGIN: aggregate :table \"tall-header\" :cols \"color vsum(quantity);'sum' count();'nb' vsum(quantity)/vmean(level);'leveled'\"\n| color  |  sum | nb | leveled | ╶╮\n| <l>    | <r7> |    |         |  ├──╮\n| kolor  | kiom |    |         | ╶╯  │  ╭────────────────────────╮\n|--------+------+----+---------|     │  │ best attempt to recover│\n| yellow |   85 |  2 |    42.5 |     ╰──┤ the three header rows  │\n| green  |   55 |  1 |      11 |        │ in the output          │\n| orange |   80 |  1 |      40 |        ╰────────────────────────╯\n#+END:\n#+end_example\n\nNote that the last aggregated column has just =leveled= in its header.\nThis is because this column refers to more than one source columns,\nnamely =quantity= and =level=.\n\nNote that in this example, there are formatting cookies:\n: <> <l> <c> <r> <7> <l7> <c7> <r7>\n\nData rows containing at least one cookie are ignored. They are not\nignored in the header.\n\n** Custom column names\n:PROPERTIES:\n:CUSTOM_ID: custom-column-names\n:END:\nIn this example, output column have names which are difficult to\nhandle:\n\n#+begin_example\n#+BEGIN: aggregate :table original :cols \"Day vmean(Level*2) vsum(Quantity^2)\"\n                                              ╰─────┬──────╯ ╰──────┬───────╯\n                    ╭───────────────────────────────╯               │\n                    │                 ╭─────────────────────────────╯\n              ╭─────┴──────╮   ╭──────┴───────╮\n| Day       | vmean(Level*2) | vsum(Quantity^2) |\n|-----------+----------------+------------------|\n| Monday    |             55 |              130 |\n| Tuesday   |             86 |              693 |\n| Wednesday |             36 |             1010 |\n| Thursday  |             86 |             2317 |\n| Friday    |             16 |              170 |\n#+END\n#+end_example\n\nWe can give them custom names with the =;'custom name'= decoration:\n\n#+begin_example\n#+BEGIN: aggregate :table original :cols \"Day vmean(Level*2);'mean2' vsum(Quantity^2);'sum_squares'\"\n                                                             ╰──┬──╯                  ╰─────┬─────╯\n                ╭───────────────────────────────────────────────╯                           │\n                │         ╭─────────────────────────────────────────────────────────────────╯\n              ╭─┴─╮   ╭───┴─────╮\n| Day       | mean2 | sum_squares |\n|-----------+-------+-------------|\n| Monday    |    55 |         130 |\n| Tuesday   |    86 |         693 |\n| Wednesday |    36 |        1010 |\n| Thursday  |    86 |        2317 |\n| Friday    |    16 |         170 |\n#+END\n#+end_example\n\nDecorators are optional.\n\n* Formatters\n:PROPERTIES:\n:CUSTOM_ID: formatters\n:END:\n\n** Org Mode compatible formatters\n:PROPERTIES:\n:CUSTOM_ID: org-mode-compatible-formatters\n:END:\nAn expression may optionally be followed by modifiers and formatters,\nafter a semicolon. Examples:\n\n#+begin_example\nvsum(X);p20    ;; increase Calc internal precision to 20 digits\nvsum(X);f3     ;; output the result with 3 digits after the decimal dot\nvsum(X);%.3f   ;; output the result with 3 digits after the decimal dot\n#+end_example\n\nThe modifiers and formatters are fully compatible with those of the\nOrg Mode spreadsheet.\n\n- =p12= change the precision to 12 decimal digits.\n- =n7= output as floating point number with 7 decimal digits.\n- =f4= output number with 4 decimal places after dot.\n- =s5= output number in \"scientific\" mode (with exponent of 10) with 5\n  decimal digits.\n- =e6= output number in \"engineering\" mode (with exponent of 10 multiple\n  of 3) with 6 decimal digits.\n- =t= output duration in decimal hours; input is supposed to be either a\n  duration like =\"2:37\"= meaning 2 hours and 37 minutes, or a number of\n  seconds like =\"1234=\" which is approximately =0.34= hours. The output is\n  controlled by the =org-table-duration-custom-format= variable.\n- =T= output duration in an hours-minutes-seconds format like =\"01:20:34\"=\n  meaning 1 hour, 20 minutes, and 34 seconds.\n- =U= like =t=, but disregard the =org-table-duration-custom-format=\n  variable and use =hh:mm= in place.\n- =N= output number: remove any non-numeric output.\n- =E= keep empty input cells. The result is often =nan=. Without =E=, empty\n  input cells are ignored as if they did not exist.\n- =D= angles are in degrees.\n- =R= angles are in radians.\n- =F= output is presented as a fraction of integers if it actually\n  is. The format is the Calc one, for example =\"2:3\"= means =2/3=.\n- =S= symbolic mode. When an input cell is, for instance =sqrt(2)=, it it\n  kept as-is rather than being replaced by =1.41421=.\n\n** Debugging formatters\n:PROPERTIES:\n:CUSTOM_ID: debugging-formatters\n:END:\nAdditionally, a few formatters are dedicated to debugging:\n\n- =c= output the Calc expression before substitution by actual input\n  cells values.\n- =q= output the Lisp expression before substitution by actual input\n  cells values.\n- =C= output the Calc expression before it gets simplified and folded.\n- =Q= output the Lisp expression before it gets simplified and folded.\n\nSee [[#debugging][Debugging]] for a detailed explanation.\n\n** Discarding an output column\n:PROPERTIES:\n:CUSTOM_ID: discarding-an-output-column\n:END:\nWhy would anyone specify a column just to discard it in the output? For\nits side effects. For sorting the output table or for adding hlines to\nit.\n\nTo discard a column, add a =;<>= modifier to the column\ndescription. This syntax is reminiscent of the =<n>= cookies in Org Mode\ntables, which instructs to shorten a column width to only =n=\ncharacters.\n\nIn this example, input hlines create a =hline= column which is used to\nadd hlines to the output. Then this =hline= column is discarded with =<>=.\n\n#+begin_example\ninvisible╶────────────────────────────────────────────╮\nsorted numerically increasing╶──────────────────────╮ │\n                                                    │ │\n                                                    ▼ ▼\n#+BEGIN: aggregate :table \"withhline\" :cols \"hline;^n;<> cölØr vsum(vâluε)\" :hline 1\n| cölØr  | vsum(vâluε) |\n|--------+-------------|\n| Red    |         7.4 |\n| Yellow |         9.1 |\n|--------+-------------|\n| Blue   |        15.7 |\n| Yellow |         5.4 |\n|--------+-------------|\n| Blue   |         4.9 |\n| Red    |         3.9 |\n| Yellow |          9. |\n|--------+-------------|\n| Red    |         1.1 |\n| Yellow |         3.4 |\n#+END:\n#+end_example\n\nHere is an example where rows are sorted on the =cölØr= column, but\nwithout displaying this column:\n\n#+begin_example\ninvisible╶────────────────────────────────────────────╮\nsorted alphabetically╶──────────────────────────────╮ │\n                                                    │ │\n                                                    ▼ ▼\n#+BEGIN: aggregate :table \"withhline\" :cols \"cölØr;^a;<> vâluε;^n\" :hline 1\n| vâluε |                                                       ▲\n|-------|                                                       │\n|   4.9 |           within the same cölØr bucket,               │\n|   7.0 |           sort vâluε numerically╶─────────────────────╯\n|   8.7 |\n|-------|\n|   1.1 |\n|   1.3 |\n|   2.6 |\n|   3.5 |\n|   3.9 |\n|-------|\n|   2.4 |\n|   3.4 |\n|   5.4 |\n|   6.6 |\n|   9.1 |\n#+END:\n#+end_example\n\n* Sorting\n:PROPERTIES:\n:CUSTOM_ID: sorting\n:END:\n\n** Example with one sorting column\n:PROPERTIES:\n:CUSTOM_ID: example-with-one-sorting-column\n:END:\nIn this example, the output table is sorted numerically on its second\ncolumn (look at the =^n= specification):\n\n#+begin_example\n#+begin: aggregate :table \"original\" :cols \"Day vsum(Quantity);^n\"\n| Day       | vsum(Quantity) |\n|-----------+----------------|\n| Monday    |             14 |\n| Friday    |             22 |\n| Tuesday   |             45 |\n| Wednesday |             54 |\n| Thursday  |             83 |\n#+end:\n#+end_example\n\nBy default, no sorting is done. The output rows follows the ordering\nof the input rows.\n\nAny column specification in the =:cols= parameter may be followed by a\nsemicolon and caret characters, and an ordering.\n\nThe specification for the ordering are the same as in Org Mode:\n- =a=: ascending alphabetical sort\n- =A=: descending alphabetical sort\n- =n=: ascending numerical sort\n- =N=: descending numerical sort\n- =t=: ascending date, time, or duration sort\n- =T=: descending date, time, or duration sort\n- =f= & =F= specifications are not (yet) implemented\n\n** Several sorting columns\n:PROPERTIES:\n:CUSTOM_ID: several-sorting-columns\n:END:\n\nThe rows of the resulting table may be sorted on any combination of\nits columns.\n\nSeveral columns may get a sorting specification. The major column is\nused for sorting. Only when two rows are equal regarding the major\ncolumn, the second major column is compared. And if the two rows are\nstill equal on this second column, the third is used, and so on.\n\nThe first sorted column in the =:cols= parameter is the major one. To\ndeclare another one as the major, follow it with a number, for\ninstance =1=. Columns without a number are minor ones.\n\nExample:\n#+begin_example\n:cols \"AAA;^a BBB;^N2 CCC DDD;^t1\"\n╭────╮      ▲╷     ▲▲          ▲▲ ╭──────────────╮\n│sort╰──────╯╰─╮   ││          │╰─╯first priority│\n│alphabetically│   ││          ╰──╮sort by date  │\n│third priority│   ││             ╰──────────────╯\n╰──────────────╯   ││  ╭────────────────╮\n                   │╰──╯second priority │\n                   ╰───╮sort numerically│\n                       │decreasing      │\n                       ╰────────────────╯\n#+end_example\n\n- Column =DDD= is sorted in ascending dates or times (=t=\n  specification). It is the major sorting column (because of its =1=\n  numbering).\n- Column =BBB= sorts rows which compare equal on column =DDD= (because of\n  its =2= numbering). This column is assumed to contain numerical\n  values, and it is sorted in descending order (=N= specification).\n- Column =AAA= is used to sort rows which compare equal regarding =DDD=\n  and =BBB=. It is sorted in ascending alphabetical order (=a=\n  specification).\n\nBoth a format and a sorting instruction may be given. Example:\n#+begin_example\n:cols \"EXPR:f3:^n\"\n#+end_example\n\nThe =EXPR= column is\n- formatted with 3 digits after dot (=f3=)\n- sorted numerically in ascending order (=^n=).\n\n* hlines in the output table\n:PROPERTIES:\n:CUSTOM_ID: hlines-in-the-output-table\n:END:\n\nThe =:hline N= parameter controls horizontal lines in the output\ntable. It may or may not be related to horizontal lines in the input.\n\n** Output hlines depends on sorting columns\n:PROPERTIES:\n:CUSTOM_ID: output-hlines-depends-on-sorting-columns\n:END:\nExample of an input table:\n\n#+begin_example\n#+name: withouthline\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Yellow |   9.1 |     95 |\n| Red    |   2.6 |     84 |\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Yellow |   5.4 |     17 |\n| Blue   |   4.9 |     64 |\n| Red    |   3.9 |     51 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n| Red    |   1.1 |     58 |\n| Yellow |   3.4 |     51 |\n#+end_example\n\nHorizontal lines appear on the sorted column, which in this example is\nthe =cölØr= column.\n\nWe require output hlines with =:hline 1=. The =1= value here says that\nonly one sorted column should be considered when drawing output\nhorizontal lines. A value of =2= would mean to consider two sorted\ncolumns.\n\nHorizontal lines will separate blocks of identical =cölØr= rows:\n\n#+begin_example\n#+BEGIN: aggregate :table \"withouthline\" :cols \"cölØr;^a vâluε 'ra;han'\" :hline 1\n| cölØr  | vâluε | 'ra;han' |                   ╰─┬─╯  ▲                  ╰─┬─╯\n|--------+-------+----------|                     │    │                    │\n| Blue   |   8.7 |       52 |╶╮                   │    │╭──────╮  ╭─────────┴─────╮\n| Blue   |   7.0 |       29 | ├─╴Blue bucket      │    ╰╯sort  │  │ separate      │\n| Blue   |   4.9 |       64 |╶╯                   ╰─────╮cölØr │  │ cölØr buckets │\n|--------+-------+----------| ◀────────────────╮        ╰──────╯  │ with hlines   │\n| Red    |   1.3 |       41 |╶╮                │                  ╰──┬┬───────────╯\n| Red    |   3.5 |       35 | │                ╰─────────────────────╯│\n| Red    |   2.6 |       84 | ├─╴Red bucket                           │\n| Red    |   3.9 |       51 | │                                       │\n| Red    |   1.1 |       58 |╶╯                                       │\n|--------+-------+----------| ◀───────────────────────────────────────╯\n| Yellow |   9.1 |       95 |╶╮\n| Yellow |   5.4 |       17 | │\n| Yellow |   2.4 |       55 | ├─╴Yellow bucket\n| Yellow |   6.6 |       34 | │\n| Yellow |   3.4 |       51 |╶╯\n#+END:\n#+end_example\n\n** Example with hline 2\n:PROPERTIES:\n:CUSTOM_ID: example-with-hline-2\n:END:\n\nIn the following example, we specify =:hline 2=.\n\nFirst, the input table now have horizontal lines. We want to propagate\nthem to the output.\n\n#+begin_example\n#+name: withhline\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Yellow |   9.1 |     95 |\n| Red    |   2.6 |     84 |\n|--------+-------+--------|\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Yellow |   5.4 |     17 |\n|--------+-------+--------|\n| Blue   |   4.9 |     64 |\n| Red    |   3.9 |     51 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n|--------+-------+--------|\n| Red    |   1.1 |     58 |\n| Yellow |   3.4 |     51 |\n#+end_example\n\nThe two sorted columns are =hline= and =cölØr=. Therefore output\nhorizontal lines separate blocks of identical =hline= and =cölØr=:\n\n#+begin_example\n#+begin: aggregate :table \"withhline\" :cols \"hline;^n cölØr;^a vâluε 'ra;han'\" :hline 2\n| hline | cölØr  | vâluε | 'ra;han' |               ▲        ▲                        ▲\n|-------+--------+-------+----------|               │        │                        │\n|     0 | Red    |   1.3 |       41 |        ╭──────╯        ╰───────────╮            │\n|     0 | Red    |   3.5 |       35 |        │ two sorted output columns │            │\n|     0 | Red    |   2.6 |       84 |        ╰───────────────────────────╯            │\n|-------+--------+-------+----------|                                                 │\n|     0 | Yellow |   9.1 |       95 |                                                 │\n|-------+--------+-------+----------|             ╭─────────────────────────────╮     │\n|     1 | Blue   |   8.7 |       52 |             │ 2 means: create hlines      ├─────╯\n|     1 | Blue   |   7.0 |       29 |             │ for buckets and sub-buckets │\n|-------+--------+-------+----------|             ╰─────────────────────────────╯\n|     1 | Yellow |   5.4 |       17 |\n|-------+--------+-------+----------|╶─╮\n|     2 | Blue   |   4.9 |       64 |  │   ╭──────────────────────────╮\n|-------+--------+-------+----------| ╶┤   │ bucket hline=2           │\n|     2 | Red    |   3.9 |       51 |  ├───┤ divided in 3 sub-buckets │\n|-------+--------+-------+----------| ╶┤   │ Blue, Red, Yellow        │\n|     2 | Yellow |   2.4 |       55 |  │   ╰──────────────────────────╯\n|     2 | Yellow |   6.6 |       34 |  │\n|-------+--------+-------+----------|╶─╯\n|     3 | Red    |   1.1 |       58 |\n|-------+--------+-------+----------|\n|     3 | Yellow |   3.4 |       51 |\n#+end:\n#+end_example\n\nAnd the =hline= column may be discarded (but its side effect\nremains). To do so use the =;<>= specifier:\n\n#+begin_example\n#+begin: aggregate :table \"withhline\" :cols \"hline;^n;<> cölØr;^a vâluε 'ra;han'\" :hline 2\n| cölØr  | vâluε | 'ra;han' |\n|--------+-------+----------|\n| Red    |   1.3 |       41 |\n| Red    |   3.5 |       35 |\n| Red    |   2.6 |       84 |\n|--------+-------+----------|\n| Yellow |   9.1 |       95 |\n|--------+-------+----------|\n| Blue   |   8.7 |       52 |\n| Blue   |   7.0 |       29 |\n|--------+-------+----------|\n| Yellow |   5.4 |       17 |\n|--------+-------+----------|\n| Blue   |   4.9 |       64 |\n|--------+-------+----------|\n| Red    |   3.9 |       51 |\n|--------+-------+----------|\n| Yellow |   2.4 |       55 |\n| Yellow |   6.6 |       34 |\n|--------+-------+----------|\n| Red    |   1.1 |       58 |\n|--------+-------+----------|\n| Yellow |   3.4 |       51 |\n#+end:\n#+end_example\n\nThe =:hline= parameter accepts a number:\n- =:hline 0=, =:hline no=, =:hline nil=, or no =:hline= mean that there will\n  be no hlines in the output.\n- =:hline 1=, =:hline yes=, =:hline t= mean that hlines will separate blocks\n  of identical rows regarding the major sorted column. In case no\n  column is sorted, then output hlines will reflect input ones.\n- =:hline 2= means that the major and the next major sorted columns will\n  be used to separate identical rows regarding those two columns.\n- =:hline 3=, =:hline 4=, ... may be specified, but they may result in too\n  much hlines.\n\n* Cells processing\n:PROPERTIES:\n:CUSTOM_ID: cells-processing\n:END:\n\n*Calc* is the standard Emacs desktop calculator. Actual mathematical\ncomputations are handled through Calc. This offers a lot of\nflexibility.\n\n** Where Calc interpretation happens?\n:PROPERTIES:\n:CUSTOM_ID: where-calc-interpretation-happens\n:END:\n\nExample of input table. Besides numbers, there are cells with\nmathematical expressions like =20*30=, or just labels as =Red&Green=\nwithout any mathematical meaning.\n\n#+begin_example\n#+name: to_Calc_or_not_to_Calc\n| Day       | Color      | Level  |\n|-----------+------------+--------|\n| Monday    | Red        | 20*30  |\n| Monday    | Blue       | 55+45  |\n| Tuesday   | Red        | 1      |\n| Tuesday   | Red&Green  | 2      |\n| Tuesday   | Blue+Green | 3      |\n| Wednesday | Red        | (27)   |\n| Wednesday | Red        | (12+1) |\n| Wednesday | Green      | [15]   |\n#+end_example\n\nBasically, Calc operates twice. For example in the formula\n=vsum(Level)=:\n- Calc computes =Level= for every input cell in the =Level= column,\n- then Calc computes =vsum()= applied to the resulting list.\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vsum(Level)\"\n| Day       | vsum(Level) |\n|-----------+-------------|\n| Monday    |         700 |\n| Tuesday   |           6 |\n| Wednesday |          55 |\n#+END:\n#+end_example\n\nThere are a few occasions were Calc computation does not happen:\n=vcount()= and =vlist(X)=.\n\nThe =vcount()= sub-formula is evaluated as the number of input rows in\neach group, without Calc intervention. However, later on Calc can\nhandle this number in a formula as this one: =vsum(Level)/vcount()=\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vcount() vsum(Level)/vcount()\"\n| Day       | vcount() | vsum(Level)/vcount() |\n|-----------+----------+----------------------|\n| Monday    |        2 |                  350 |\n| Tuesday   |        3 |                    2 |\n| Wednesday |        3 |            18.333333 |\n#+END:\n#+end_example\n\nAnd of course when input cells do not have a mathematical meaning, the\nresult may be non-sens:\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vsum(Color)\"\n| Day       | vsum(Color)                                    |\n|-----------+------------------------------------------------|\n| Monday    | Red + Blue                                     |\n| Tuesday   | Red + error(3, '\"Syntax error\") + Blue + Green |\n| Wednesday | 2 Red + Green                                  |\n#+END:\n#+end_example\n\nBut it can also make sens. The last row, which aggregate =Wednesday=\nrows, is computed as =2 Red + Green=. This is correct. This symbolic\nresult (as opposed to numerical result) shows the power of Calc as a\nsymbolic calculator.\n\nThe =vlist(X)= formula is not handled by Calc at all. This formula\nmust appear alone (not embedded as part of a bigger formula). The cells\n=X= are not interpreted by Calc. As a result, =vlist(X)= produces a\ncell which concatenates input cells verbatim. For instance, the input\ncell =20*30= is left as-is.\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vlist(Color) vlist(Level)\"\n| Day       | vlist(Color)               | vlist(Level)       |\n|-----------+----------------------------+--------------------|\n| Monday    | Red, Blue                  | 20*30, 55+45       |\n| Tuesday   | Red, Red&Green, Blue+Green | 1, 2, 3            |\n| Wednesday | Red, Red, Green            | (27), (12+1), [15] |\n#+END:\n#+end_example\n\nAs a contrast, the formula =(Level)= yields a list processed through\nCalc. For instance, the =20*30= formula is replaced by =600=.\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day (Color) (Level)\"\n| Day       | (Color)                                        | (Level)        |\n|-----------+------------------------------------------------+----------------|\n| Monday    | [Red, Blue]                                    | [600, 100]     |\n| Tuesday   | [Red, error(3, '\"Syntax error\"), Blue + Green] | [1, 2, 3]      |\n| Wednesday | [Red, Red, Green]                              | [27, 13, [15]] |\n#+END:\n#+end_example\n\nHere we used parenthesis in =(Color)= and =(Level)= because otherwise\nthey would have been /key columns/. Instead of parenthesis, we can\nembed such expressions in formulas, like =Level+1=:\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day Level+1\"\n| Day       | Level+1        |\n|-----------+----------------|\n| Monday    | [601, 101]     |\n| Tuesday   | [2, 3, 4]      |\n| Wednesday | [28, 14, [16]] |\n#+END:\n#+end_example\n\nTo summarize, a column name embedded in a formula is evaluated as the\nlist of input cells, processed by Calc. Except for the =vlist(Column)=\nformula where input cells are kept verbatim.\n\nBy the way, what is the meaning of the expression =Level*Level=? For\n=Monday=, it is =[600,100]*[600,100]=. Then Calc simplifies that as a\n/vector product/: sum of individual products. =600^2+100^2=\n\n#+begin_example\n#+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day Level*Level Level+Level\"\n| Day       | Level*Level | Level+Level    |\n|-----------+-------------+----------------|\n| Monday    |      370000 | [1200, 200]    |\n| Tuesday   |          14 | [2, 4, 6]      |\n| Wednesday |        1123 | [54, 26, [30]] |\n#+END:\n#+end_example\n\n** Dates\n:PROPERTIES:\n:CUSTOM_ID: dates\n:END:\n\nSome aggregations are possible on dates. Example. Here is a source\ntable containing dates:\n\n#+begin_example\n#+tblname: datetable\n| Date                   |\n|------------------------|\n| [2035-12-22 Sat 09:01] |\n| [2034-11-24 Fri 13:04] |\n| [2030-09-24 Tue 13:54] |\n| [2027-09-25 Sat 03:54] |\n| [2023-02-26 Sun 16:11] |\n| [2020-03-17 Tue 03:51] |\n| [2018-08-21 Tue 00:00] |\n| [2012-12-25 Tue 00:00] |\n#+end_example\n\nHere are the earliest and the latest dates, along with the average of\nall input dates:\n\n#+begin_example\n#+BEGIN: aggregate :table datetable :cols \"vmin(Date) vmax(Date) vmean(Date)\"\n| vmin(Date)             | vmax(Date)             | vmean(Date) |\n|------------------------+------------------------+-------------|\n| <2012-12-25 Tue 00:00> | <2035-12-22 Sat 09:01> |   739448.44 |\n#+END:\n#+end_example\n\nThe average of all dates is a number? Actually, it is a date expressed\nas the number of days since =[0000-12-31 Sun 00:00]=. To force a\nnumber of days to be interpreted as a date, use the =date()= function:\n\n#+begin_example\n#+BEGIN: aggregate :table datetable :cols \"date(vmean(Date))\"\n| date(vmean(Date))      |\n|------------------------|\n| <2025-07-16 Wed 10:29> |\n#+END:\n#+end_example\n\nWith the =date()= function in mind, all kinds of dates handling can be\ndone. Example: the average of earliest and the latest dates is\ndifferent from the average of all dates:\n\n#+begin_example\n#+BEGIN: aggregate :table datetable :cols \"date(vmean(vmin(Date),vmax(Date))) date(vmean(Date))\"\n| date(vmean(vmin(Date),vmax(Date))) | date(vmean(Date))      |\n|------------------------------------+------------------------|\n| <2024-06-23 Sun 16:30>             | <2025-07-16 Wed 10:29> |\n#+END:\n#+end_example\n\nNote that =date()= is not special to OrgAggregate. It can be used in\nOrg Mode spreadsheet formulas.\n\n** Durations\n:PROPERTIES:\n:CUSTOM_ID: durations\n:END:\nIn Org Mode spreadsheet, durations have the forms =HH:MM= or\n=HH:MM:SS=. In OrgAggregate, when an input cell have one of those two\nforms, it is converted into a number of seconds. For instance, =01:00=\nis converted into =3600= and =00:00:07= is converted into =7=.\n\nThere may be a single digit for hours, as in =7:12= or more than two\nas in =1255:45:00=.\n\nTo output such a form, use a formatter: =;T=; =;t=, =;U=. For example, we\nhave 3 durations as input, and we want the average of them:\n\n#+begin_example\n#+name: some_durations\n|      dur |\n|----------|\n| 07:45:30 |\n|    13:55 |\n|    17:12 |\n#+end_example\n\n#+begin_example\n#+BEGIN: aggregate :table \"some_durations\" :cols \"vmean(dur) vmean(dur);T vmean(dur);t vmean(dur);U\"\n| vmean(dur) | vmean(dur) | vmean(dur) | vmean(dur) |\n|------------+------------+------------+------------|\n|      46650 |   12:57:30 |      12.96 |      12:57 |\n#+END:\n#+end_example\n\n- With no formatter, we get a number of seconds\n- The =T= formatter outputs the result as =HH:MM:SS=\n- The =U= formatter outputs the result as =HH:MM=\n- The =t= formatter converts the result into a number of hours (it\n  divides the number of seconds by 3600, and displays only two digits\n  after dot)\n\nThe Calc syntax for durations is also recognized:\n#+begin_example\nHH@ MM'\nHH@ MM' SS\"\n#+end_example\nExample:\n\n#+begin_example\n#+name: calc_durations\n| dur        |\n|------------|\n| 07@ 45' 30 |\n| 13@ 55'    |\n| 17@ 12'    |\n#+end_example\n\n#+begin_example\n#+BEGIN: aggregate :table \"calc_durations\" :cols \"vmean(dur)\"\n| vmean(dur)   |\n|--------------|\n| 12@ 57' 30.\" |\n#+END:\n#+end_example\n\n** Empty and malformed input cells\n:PROPERTIES:\n:CUSTOM_ID: empty-and-malformed-input-cells\n:END:\n\nThe input table may contain malformed mathematical text.  For\ninstance, a cell containing =5+= is malformed, because an expression\nis missing after the =+= symbol.  In this case, the value will be\nreplaced by =error(2, '\"Expected a number\")= which will appear in the\naggregated table, signaling the problem.\n\nAn input cell may be empty.  In this case, it may be ignored or\nconverted to zero, depending on modifier flags =E= and =N=.\n\nThe empty cells treatment\n- makes no difference for =vsum= and =count=.\n- may result in zero for =prod=,\n- change =vmean= result,\n- change =vmin= and =vmax=, a possibly empty list of values resulting in\n  =inf= or =-inf=\n\nSome aggregation functions operate on two columns.  If the two columns\nhave empty values at different locations, then they should be\ninterpreted as zero with the =NE= modifier, otherwise the result will\nbe inconsistent.\n\nSometimes an input table may be malformed, with incomplete rows, like\nthis one:\n\n#+begin_example\n| Color | Level | Quantity | Day       |\n|-------+-------+----------+-----------|\n| Red   |    30 |       11 | Monday    |\n| Blue  |    25 |        3 | Monday    |\n|\n| Blue  |    33 |       18 | Tuesday   |\n| Red   |    27 |\n| Blue  |    12 |       16 | Wednesday |\n| Blue  |    15 |       15 |\n|\n#+end_example\n\nMissing cells are handled as though they were empty.\n\n** Symbolic computation\n:PROPERTIES:\n:CUSTOM_ID: symbolic-computation\n:END:\n\nThe computations are based on Calc, which is a symbolic calculator.\nThus, symbolic computations are built-in. Example:\n\nThis is the source table:\n\n#+begin_example\n#+NAME: symtable\n| Day       | Color |  Level | Quantity |\n|-----------+-------+--------+----------|\n| Monday    | Red   |   30+x |     11+a |\n| Monday    | Blue  | 25+3*x |        3 |\n| Tuesday   | Red   | 51+2*x |       12 |\n| Tuesday   | Red   |   45-x |       15 |\n| Tuesday   | Blue  |     33 |       18 |\n| Wednesday | Red   |     27 |       23 |\n| Wednesday | Blue  |   12+x |       16 |\n| Wednesday | Blue  |     15 |   15-6*a |\n| Thursday  | Red   |     39 |   24-5*a |\n| Thursday  | Red   |     41 |       29 |\n| Thursday  | Red   |   49+x |   30+9*a |\n| Friday    | Blue  |      7 |      5+a |\n| Friday    | Blue  |      6 |        8 |\n| Friday    | Blue  |     11 |        9 |\n#+end_example\n\nAnd here is the aggregated, symbolic result:\n\n#+begin_example\n#+BEGIN: aggregate :table \"symtable\" :cols \"Day vmean(Level) vsum(Quantity)\"\n| Day       | vmean(Level)          | vsum(Quantity) |\n|-----------+-----------------------+----------------|\n| Monday    | 2. x + 27.5           | a + 14         |\n| Tuesday   | 0.333333333334 x + 43 | 45             |\n| Wednesday | x / 3 + 18            | 54 - 6 a       |\n| Thursday  | x / 3 + 43.           | 4 a + 83       |\n| Friday    | 8                     | a + 22         |\n#+END\n#+end_example\n\nSymbolic calculations are correctly performed on =x= and =a=, which\nare symbolic (as opposed to numeric) expressions.\n\nNote that if there are empty cells in the input, they will be changed\nto =nan= /not a number/, and the whole aggregation will yield =nan=.  This\nis probably not the expected result.\n\nThe =N= modifier (see [[#org-mode-compatible-formatters][Org Mode compatible formatters]]) won't help,\nbecause even though it will replace empty cells with zero, it will do\nthe same for anything which does not look like a number.  The best is\nto just avoid empty cells when dealing with symbolic calculations.\n\n** Intervals\n:PROPERTIES:\n:CUSTOM_ID: intervals\n:END:\nOrg Mode tables and OrgAggregate backend engine being Emacs Calc,\nintervals are seamlessly handled. An interval is made of two numerical\nvalues separated by two dots.\n\nExample of a spreadsheet. The third column is computed by multiplying\nthe first two:\n\n#+begin_example\n,#+name: int\n| 3..5 |    10 | (30 .. 50) |\n| 3..5 | -1..2 | (-5 .. 10) |\n|    5 |     2 | 10         |\n,#+TBLFM: $3=$1*$2\n#+end_example\n\nExample of an aggregation. The sum of the third column is computed,\nresulting in an interval:\n\n#+begin_example\n,#+BEGIN: aggregate :table \"int\" :cols \"vsum($3)\"\n| vsum($3)   |\n|------------|\n| (35 .. 70) |\n,#+END:\n#+end_example\n\n** Error or precision forms\n:PROPERTIES:\n:CUSTOM_ID: error-or-precision-forms\n:END:\n\nIn Emacs Calc, an error form is a numerical value along with its\nprecision, both values separated by =+/-=.\n\nThe separation may also be the Unicode character =±=. In this\nspreadsheet example, the second column is 10 times the first:\n\n#+begin_example\n,#+name: cert\n| 12±2 | 120 +/- 20 |\n| 55±3 | 550 +/- 30 |\n| 9±0  | 90         |\n| 15±1 | 150 +/- 10 |\n| 21±1 | 210 +/- 10 |\n,#+TBLFM: $2=10*$1\n#+end_example\n\nNow, in the following example, OrgAggregate computes the sum of the\nsecond column:\n\n#+begin_example\n,#+BEGIN: aggregate :table \"cert\" :cols \"vsum($2);f2\"\n| vsum($2)       |\n|----------------|\n| 1120 +/- 38.73 |\n,#+END:\n#+end_example\n\nThe computation made by Emacs Calc assumes that all input values\nfollow a Gaussian distribution, and are independent variables. Then it\napplies the textbook formula for combining Gaussian distributions.\nBeware that your input values may not be independent with each\nother. In this case, the resulting error will be slightly off (too\nsmall).\n\n* Wide variety of inputs\n:PROPERTIES:\n:CUSTOM_ID: wide-variety-of-inputs\n:END:\n\nAs in any other Org Mode source block, the input table may come from\nseveral places. OrgAggregate adds even more kinds of input.\n\nHere we discus the =:table= parameter.\n\n** Standard Org Mode input\n:PROPERTIES:\n:CUSTOM_ID: standard-org-mode-input\n:END:\n\nThe parameter after =:table= may be:\n\n- =mytable=: an ordinary Org Mode table in the same buffer, named\n  =mytable=.\n\n- =/some/dir/file.org:mytable=: an ordinary Org Mode table named\n  =mytable=, in a distant Org file named =/some/dir/file.org=.\n\n** Virtual input table from Babel\n:PROPERTIES:\n:CUSTOM_ID: virtual-input-table-from-babel\n:END:\n\n- =mybabel=: an Org Mode Babel block named =mybabel= in the current\n  buffer, generating a table as its output, written in any language.\n\n- =mybabel(param1=123,param2=456)=: passing parameters to an Org Mode\n  Babel block named =mybabel= in the current buffer, generating a table\n  as its output, written in any language.\n\n- =/some/dir/file.org:mybabel(param1=123,param2=456)=: an Org Mode Babel\n  block named =mybabel= in a distant org file named =/some/dir/file.org=,\n  called with parameters.\n\nThe input table may be the result of executing a Babel script. In this\ncase, the table is virtual in the sense that is appears nowhere.\n\n(Babel is the Org Mode infrastructure to run scripts in any language,\nlike Python, R, C++, Java, D, Rust, shell, whatever, with inputs and\noutputs connected to Org Mode).\n\nExample:\n\nHere is a script in Emacs Lisp which creates an Org Mode table.\n\n#+begin_example\n#+name: ascript\n#+begin_src elisp :colnames yes\n`(\n  (\"label\" value)                       ; cells are symbols or strings\n  hline\n  ,@(cl-loop\n     for i from 10 to 20\n     collect\n     (list\n      (format \"lbl-%c\" (+ ?A (% i 3)))  ; cell is a string\n      i)))                              ; cell is a number\n#+end_src\n#+end_example\n\nIf executed, the script would output this table:\n\n#+begin_example\n#+RESULTS: ascript\n| label | value |\n|-------+-------|\n| lbl-B |    10 |\n| lbl-C |    11 |\n| lbl-A |    12 |\n| lbl-B |    13 |\n| lbl-C |    14 |\n| lbl-A |    15 |\n| lbl-B |    16 |\n| lbl-C |    17 |\n| lbl-A |    18 |\n| lbl-B |    19 |\n| lbl-C |    20 |\n#+end_example\n\nBut instead, OrgAggregate will execute the script and consume its\noutput:\n\n#+begin_example\n#+BEGIN: aggregate :table \"ascript\" :cols \"label vsum(value)\"\n| label | vsum(value) |\n|-------+-------------|\n| lbl-B |          58 |\n| lbl-C |          62 |\n| lbl-A |          45 |\n#+END:\n#+end_example\n\nHere the parameter =:table= specifies the name of the script to be\nexecuted.\n\n*Beware* of the =:results code= parameter. It does not work as an input\nfor OrgAggregate. This is because in this case the Babel script\nreturns a string, not a table. Example:\n\n#+begin_src elisp :results code\n'((a b c)\n  hline\n  (1 2 3))\n#+end_src\n\nUse =:results table= or nothing instead.\n\n** An Org ID\n:PROPERTIES:\n:CUSTOM_ID: an-org-id\n:END:\n\n- =34cbc63a-c664-471e-a620-d654b26ffa31=: an identifier of an Org Mode\n  sub-tree. The sub-tree is supposed to contain an Org table (which is\n  retrieved) or a Babel script (which is executed).\n\nThose Org Mode identifiers span all known Org Mode files. (Therefore\nthere is no need to specify a file). To add such an identifier, put\nthe cursor on the heading of the sub-tree, and type =M-x\norg-id-get-create=.\n\n** CSV input\n:PROPERTIES:\n:CUSTOM_ID: csv-input\n:END:\n\nCVS input is specific to OrgAggregate. (Native Org Mode does not\noffers those formats).\n\n- =/some/dir/file.csv:(csv params…)=: a comma-separated-values file in\n  the CSV format, in the file =/some/dir/file.csv=.\n\n- =name(csv params…)=: CSV formatted data within an Org block named\n  =\"name\"=, in the current file.\n\n- =/some/dir/file.org:name(csv params…)=: CSV formatted data within an\n  Org block named =\"name\"=, in a distant Org file.\n\nThe cells separators in the CSV files may be TAB, comma, or semicolon,\nthey are guessed and different separators may be mixed.\n\nAny empty row in the CSV file is interpreted as an horizontal\nseparator (=hline= in Org table parlance).\n\nParameters may be:\n  - =header=: the first row in the CSV file is interpreted as a header\n    containing the column names.\n  - =colnames (column1 column2 column3 …)=: the column names are given\n    explicitly, in case the CSV file contains only data, no header.\n  In any case, the columns may be references as =$1, $2, $3, …= as\n  usual.\n\nWhen data is in an Org Mode file, it is supposed to be within a named\nblock of any kind. Example with a \"quote\" block:\n\n#+begin_example\n#+name: mycsvdata\n#+begin_quote\nlabel,quantity\nyellow,27\nred,-61\nyellow,41\nred,-29\nred,-17\n#+end_quote\n#+end_example\n\n** JSON input\n:PROPERTIES:\n:CUSTOM_ID: json-input\n:END:\n\n- =/some/dir/file.json:(json params…)=: a file containing a JSON\n  formatted table, in the file =/some/dir/file.csv=.\n\n- =name(json params…)=: JSON formatted data within an Org block named\n  =\"name\"=, in the current file.\n\n- =/some/dir/file.org:name(json params…)=: JSON formatted data within an\n  Org block named =\"name\"=, in a distant Org file.\n\nThe accepted formats are a vector of vectors, or a vector of\nhash-objects.\n\nCurrently, no =params= are recognized.\n\nExample of a vector of vectors:\n\n#+begin_example\n[\n  [\"mon\",12,\"red\"  ],\n  [\"thu\",34,\"blue\" ],\n  [\"wed\",27,\"green\"],\n  [\"wed\",21,\"red\"  ],\n  [\"mon\", 7,\"blue\" ],\n  …\n]\n#+end_example\n\nExample of a vector of hash-objects:\n\n#+begin_example\n[\n  {\"day\":\"mon\", \"quty\":12, \"color\":\"red\"  },\n  {\"day\":\"thu\", \"quty\":34, \"color\":\"blue\" },\n  {\"quty\":27, \"day\":\"wed\", \"color\":\"green\"},\n  {\"quty\":21, \"color\":\"red\", \"day\":\"wed\"  },\n  {\"day\":\"mon\", \"quty\": 7, \"color\":\"blue\" },\n  …\n]\n#+end_example\n\nIn the case of hash-objects, the keys are collected as the header of\nthe resulting table, in the order seen in the JSON file. In each\nhash-object, the order of key-value pairs is irrelevant.\n\nA header may be specified in the JSON file as a first vector, followed\nby an hline (horizontal line). Example:\n\n#+begin_example\n[\n  [\"day\",\"quty\",\"color\"],\n  \"hline\",\n  [\"mon\",12,\"red\"  ],\n  [\"thu\",34,\"blue\" ],\n  …\n]\n#+end_example\n\nHorizontal lines may be =\"hline\"=, =[]=, ={}=, or =null=.\n\nIt is possible to mix both formats: vectors and hash-objects. Example:\n\n#+begin_example\n[\n  [\"quty\",\"color\"],                         // incomplete header\n  null,                                     // horizontal line\n  {\"day\":\"mon\", \"quty\":12, \"color\":\"red\" }, // an hash-object\n  [\"thu\", 34, \"blue\" ],                     // a vector\n  …\n]\n#+end_example\n\nWhen data is in an Org Mode file, it is supposed to be within a named\nblock of any kind. Example with a \"src\" block for JavaScript:\n\n#+begin_example\n#+name: myjsondata\n#+begin_src js\n[\n  [\"day\",\"quty\",\"color\"],\n  \"hline\",\n  [\"mon\",12,\"red\"  ],\n  [\"thu\",34,\"blue\" ],\n  …\n]\n#+end_src\n#+end_example\n\n** Input slicing\n:PROPERTIES:\n:CUSTOM_ID: input-slicing\n:END:\n\nOrg Mode also provides for table slicing. All of the previous\nreferences may be followed by an optional slicing. Examples:\n\n- =mytable[0:5]=: retain only the first 6 rows of the input table; if\n  the table has a header, then it counts as 2 rows (the header and the\n  separation line). In this example, it would retain rows 0 and 1 for\n  the header, and rows 2,3,4,5 for the content.\n\n- =mytable[*,0:1]=: retain only the first 2 columns.\n\n- =mytable[0:5,0:1]=: retain only the first 6 rows and the first 2\n  columns.\n\n** The :cond filter\n:PROPERTIES:\n:CUSTOM_ID: the-cond-filter\n:END:\n\nThis parameter is optional. If present, it specifies a Lisp expression\nwhich tells whether or not a row should be kept. When the expression\nevaluates to nil, the row is discarded.\n\nExamples of useful expressions includes:\n- =:cond (equal Color \"Red\")=\n  + to keep only rows where =Color= is =Red=\n- =:cond (> (string-to-number Quantity) 19)=\n  + to keep only rows for which =Quantity= is more than =19=\n  + note the call to =string-to-number=; without this call, =Quantity=\n    would be used as a string\n- =:cond (> (* (string-to-number Level) 2.5) (string-to-number Quantity))=\n  + to keep only rows for which =2.5*Level > Quantity=\n\nBeware with this example: =:cond (equal Color \"Red\")=. The input table\nshould not have a column named =Red=, otherwise the condition will mean:\n/keep only rows with the same value in columns Color and Red/\n\nAs a special case, when =:cols= parameter is not given, the result is\nthe same as =:cols \"COL1 COL2 COL3...\"=. All columns in the input\ntable are specified as key columns, and output in the resulting table.\n\nThis is useful when just filtering. But be aware that aggregation\nstill occurs. So duplicate input rows appear only once in the result.\n\n** Virtual input columns\n:PROPERTIES:\n:CUSTOM_ID: virtual-input-columns\n:END:\nWhat if we want to aggregate on months? But the input table contains\nonly plain dates. Example:\n\n#+begin_example\n#+name: without-months\n| Date             | Quantity |\n|------------------+----------|\n| [2037-03-12 thu] |    56.93 |\n| [2037-03-25 wed] |    20.99 |\n| [2037-04-07 tue] |    80.81 |\n| [2037-04-20 mon] |    22.26 |\n| [2037-05-03 sun] |    69.75 |\n| [2037-05-16 sat] |    39.91 |\n| [2037-05-29 fri] |    93.13 |\n| [2037-06-11 thu] |    17.11 |\n| [2037-06-24 wed] |    24.21 |\n| [2037-07-07 tue] |    82.38 |\n| [2037-07-20 mon] |    39.94 |\n| [2037-08-02 sun] |    81.90 |\n| [2037-08-15 sat] |    71.67 |\n| [2037-08-28 fri] |    82.81 |\n| [2037-09-10 thu] |    42.50 |\n| [2037-09-23 wed] |    62.52 |\n| [2037-10-06 tue] |     5.13 |\n#+end_example\n\nWe would like to specify the aggregation over =month(Date)=. But only\nplain columns may be used as aggregating keys.\n\nOne way is to add a =Month= column to the input table. The modified\ntable looks like:\n\n#+begin_example\n#+name: with-months\n| Date             | Quantity | Month |\n|------------------+----------+-------|\n| [2037-03-12 thu] |    56.93 |     3 |\n| [2037-03-25 wed] |    20.99 |     3 |\n| [2037-04-07 tue] |    80.81 |     4 |\n| [2037-04-20 mon] |    22.26 |     4 |\n| [2037-05-03 sun] |    69.75 |     5 |\n| [2037-05-16 sat] |    39.91 |     5 |\n| [2037-05-29 fri] |    93.13 |     5 |\n| [2037-06-11 thu] |    17.11 |     6 |\n| [2037-06-24 wed] |    24.21 |     6 |\n| [2037-07-07 tue] |    82.38 |     7 |\n| [2037-07-20 mon] |    39.94 |     7 |\n| [2037-08-02 sun] |    81.90 |     8 |\n| [2037-08-15 sat] |    71.67 |     8 |\n| [2037-08-28 fri] |    82.81 |     8 |\n| [2037-09-10 thu] |    42.50 |     9 |\n| [2037-09-23 wed] |    62.52 |     9 |\n| [2037-10-06 tue] |     5.13 |    10 |\n#+TBLFM: $3=month($1)\n#+end_example\n\nOrgAggregate allows adding input columns like this computed =Month=\ncolumn, without modifying the input table. The =:precompute= parameter\ndoes that. Example:\n\n#+begin_example\n#+BEGIN: aggregate :table \"without-months\" :cols \"Month vsum(Quantity)\" :precompute (\"month(Date);'Month'\")\n| Month | vsum(Quantity) |\n|-------+----------------|\n|     3 |          77.92 |\n|     4 |         103.07 |\n|     5 |         202.79 |\n|     6 |          41.32 |\n|     7 |         122.32 |\n|     8 |         236.38 |\n|     9 |         105.02 |\n|    10 |           5.13 |\n#+END:\n#+end_example\n\nThe specification =month(Date);'Month'= means:\n- add a third column to the input table,\n- fill it with the formula =month(Date)=, which is a Calc formula,\n- give this new column the =Month= title,\n- make this new column available for aggregation, as any other column.\nAll this process is virtual. The input table is not modified in any\nway.\n\nIf the title =Month= is not specified, then the new virtual column will\nbe referred to as =$3=.\n\nNote that here the title was specified with single quotes. This is\nrequired to disambiguate with the format. The syntax is consistent\nwith the one used in the =:cols= parameter and the one used by Org Mode\nspreadsheet formulas.\n\nThe pre-computations may also be Lisp expressions, exactly like in the\nusual Org table spreadsheet. In this example, we want to aggregate on\ncoarse bins. Bins are just hundredths of the first column:\n\n#+begin_example\n#+name: want-bins\n| 109 | 41.24 |\n| 140 | 40.60 |\n| 174 |  7.68 |\n| 288 | 33.96 |\n| 334 | 21.42 |\n| 418 | 74.73 |\n| 455 | 79.62 |\n| 479 | 22.23 |\n| 554 | 28.03 |\n| 678 | 64.12 |\n| 797 | 70.91 |\n| 947 | 93.48 |\n#+end_example\n\n#+begin_example\n#+BEGIN: aggregate :table \"want-bins\" :cols \"$3 vmean($2)\" :precompute (\"'(floor (/ (string-to-number $1) 100))\")\n| $3 | vmean($2) |\n|----+-----------|\n|  1 |     29.84 |\n|  2 |     33.96 |\n|  3 |     21.42 |\n|  4 |     58.86 |\n|  5 |     28.03 |\n|  6 |     64.12 |\n|  7 |     70.91 |\n|  9 |     93.48 |\n#+END:\n#+end_example\n\nVirtual columns may be formatted as any other column, with the same\nsyntax as in =:cols= or in the Org table spreadsheet. For example here\nwe give it 2 digits after dot:\n\n#+begin_example\n#+BEGIN: aggregate :table \"want-bins\" :cols \"$3\" :precompute \"floor($1/100);%.2f\"\n|   $3 |\n|------|\n| 1.00 |\n| 2.00 |\n| 3.00 |\n| 4.00 |\n| 5.00 |\n| 6.00 |\n| 7.00 |\n| 9.00 |\n#+END:\n#+end_example\n\nOf course, those additional virtual input columns may be used for\nother purposes than key columns. They may enter in aggregating\nformulas. Or they may be used by the optional row filter (the =:cond=\nparameter). There is no difference between actual and virtual columns.\n\nThe =:precompute= parameter may be:\n\n- a list of strings, example:\n: (\"month(Date);'Month'\" \"day(Date);'Day'\")\n\n- a single string with fields separated by =::=, like in the =#+tblfm:=\n  tags of a spreadsheet. Example:\n: \"month(Date);'Month' :: day(Date);'Day'\"\n\n- a string containing a single formula (actually this is a special\n  case of the previous one). Example:\n: \"month(Date);'Month'\"\n\n* Post-processing\n:PROPERTIES:\n:CUSTOM_ID: post-processing\n:END:\n\nAfter OrgAggregate has generated the output table, it can be further\nprocessed:\n- additional columns may be added with the standard Org Mode\n  spreadsheet formulas.\n- any algorithm in an exotic language (Python, R, C++, Emacs Lisp, and\n  so on) can be applied to the output.\n\n** Spreadsheet formulas\n:PROPERTIES:\n:CUSTOM_ID: spreadsheet-formulas\n:END:\n\nAdditional columns can be specified for the resulting table.  With a\nprevious example, adding a =:formula= parameter, we specify a new column\n=$4= which uses the aggregated columns.  It is translated into a usual\n=#+TBLFM:= spreadsheet line.\n\n#+begin_example\n#+BEGIN: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\" :formula \"$4=$2*$3\"\n| Day       | vmean(Level) | vsum(Quantity) |      |\n|-----------+--------------+----------------+------|\n| Monday    |         27.5 |             14 | 385. |\n| Tuesday   |           43 |             45 | 1935 |\n| Wednesday |           18 |             54 |  972 |\n| Thursday  |           43 |             83 | 3569 |\n| Friday    |            8 |             22 |  176 |\n#+TBLFM: $4=$2*$3\n#+END:\n#+end_example\n\nMoreover, if a =#+TBLFM:= was already there, it survives aggregation re-computations.\n\nThis happens in /pull mode/ only.\n\n** Algorithm post processing\n:PROPERTIES:\n:CUSTOM_ID: algorithm-post-processing\n:END:\n\nThe aggregated table can be post-processed with the =:post=\nparameter. It accepts a Lisp =lambda=, a Lisp function, or a Babel block\nin any exotic language (R, Python, C++, Emacs Lisp and so on).\n\nThe process receives the aggregated table as parameter in the form of\na Lisp expression. It can process it in any way it wants, provided it\nreturns a valid Lisp table.\n\nA Lisp table is a list of rows. Each row is either a list of cells, or\nthe special symbol =hline=.\n\nIn this example, a =lambda= expression adds a =hline= and a row for /Sunday/.\n\n#+begin_example\n#+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\" :post (lambda (table) (append table '(hline (Sunday \"0.0\"))))\n| Day       | vsum(Quantity) |\n|-----------+----------------|\n| Monday    |             14 |\n| Tuesday   |             45 |\n| Wednesday |             54 |\n| Thursday  |             83 |\n| Friday    |             22 |\n|-----------+----------------|\n| Sunday    |            0.0 |\n#+END:\n#+end_example\n\nThe =lambda= can be moved to a =defun=. The function is then passed to the\n=:post= parameter:\n\n#+begin_example\n,#+begin_src elisp\n(defun my-function (table)\n  (append table\n          '(hline (Sunday \"0.0\"))))\n,#+end_src\n\n... :post my-function\n#+end_example\n\nThe =:post= parameter can also refer to a Babel Block. Example:\n\n#+begin_example\n#+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\" :post \"my-babel-block(tbl=*this*)\"\n...\n#+END:\n#+end_example\n\n#+begin_example\n,#+name: my-babel-block\n,#+begin_src elisp :var tbl=\"\"\n(append tbl\n        '(hline (Sunday \"0.0\")))\n,#+end_src\n#+end_example\n\n*Beware!* You may want to add =:colnames t= to your Babel block. Otherwise\nthe table's header will be lost.\n\n** Grand total\n:PROPERTIES:\n:CUSTOM_ID: grand-total\n:END:\n\nShe (the user) may be tempted to add the grand total at the bottom of\nthe aggregation. Example of such a temptation:\n\n#+begin_example\n,#+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n| Day       | vmean(Level) | vsum(Quantity) |\n|-----------+--------------+----------------|\n| Monday    |         27.5 |             14 |\n| Tuesday   |           43 |             45 |\n| Wednesday |           18 |             54 |\n| Thursday  |           43 |             83 |\n| Friday    |            8 |             22 |\n|-----------+--------------+----------------|\n| Total     |              |            218 |\n,#+TBLFM: @7$3=vsum(@I..@II)\n,#+end\n#+end_example\n\nWith OrgAggreagate post-processing, it is easy.\n1. Just put in place a small algorithm to add two lines.\n\n#+begin_example\n:post (append *this* '(hline (\"Total\" \"\" \"\")))\n                ▲        ▲               ▲\n                ╰─────╮  │               │\nthe aggregated table╶─╯  │               │\none horizontal line╶─────╯               │\nan empty cell to recieve the total╶──────╯\n#+end_example\n\nThe =*this*= Lisp variable contains the aggregated table, just while the\npost-processing takes place. The =append= Lisp function adds two rows to\nthe aggregated table, and returns the amended table.\n\nNote that the =:post= parameter may be:\n- a small Lisp expression, as in this example,\n- a lambda expression, which parameter is the aggregated table,\n- the name of a Lisp function, which is passed the aggregate table,\n- the name of a Babel block, written in any supported language.\n\n2. Fill the additional cell with a formula for the total.\n\n#+begin_example\n@>$2=vsum(@I..@II)\n▲ ▲   ▲   ▲   ▲\n│ │   │   │   ╰──────────────╮\n│ │   │   ╰────────────────╮ │\n│ │   ╰─────────────────╮  │ │\n│ ╰────────────╮        │  │ │\n╰──────────╮   │        │  │ │\nlast line╶─╯   │        │  │ │\nsecond column╶─╯        │  │ │\nsum all values between╶─╯  │ │\nthe first horizontal line╶─╯ │\nand the second one╶──────────╯\n#+end_example\n\nIn this way, the grand-total will be recomputed each time the\naggregation is refreshed (=C-c C-c=). Note the use of =@>$2= for the\ncoordinates of the cell receiving the total, instead of, for instance\n=@7$3=. This ensures that the formula will continue to be applied on the\nlast row, even if the aggregated table grows later on. The same idea\napplies for the =@I..@II= range, instead of, for instance =@2..@6=.\n\nEven though OrgAggregate offers the user a versatil post-processing to\nadd a grand total, there are many reasons not to. If she does, she is\nquietly entering the same nightmare which plagues spreadsheets.\nEverything will become harder and harder to maintain.\n\nIt seems natural to add a grand total right below the column. But\nsuppose that now she also want the standard deviation of this same\ncolumn. Where to put it? There is a blank cell just left of the grand\ntotal. She puts the standard deviation there:\n\n#+begin_example\n#+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n| Day       | vmean(Level) | vsum(Quantity) |\n|-----------+--------------+----------------|\n| Monday    |         27.5 |             14 |\n| Tuesday   |           43 |             45 |\n| Wednesday |           18 |             54 |\n| Thursday  |           43 |             83 |\n| Friday    |            8 |             22 |\n|-----------+--------------+----------------|\n| Total     |    27.409852 |            218 |\n#+TBLFM: @7$3=vsum(@I..@II)::@7$2=vsdev(@I$3..@II$3)\n#+end\n#+end_example\n\nBut then this =27.409852= looks like the grand total of the second\ncolumn, but it is not.\n\nLater on, she may want to further process this aggregated table, for\nexample to plot it. The grand total row will be an annoyance. It will\nbe tedious to get ride of it.\n\nShe should instead consider creating a second aggregation with the\ngrand total:\n\n#+begin_example\n,#+begin: aggregate :table original :cols \"vsdev(Level) vsum(Quantity)\"\n| vsdev(Level) | vsum(Quantity) |\n|--------------+----------------|\n|    27.409852 |            218 |\n,#+end\n#+end_example\n\nEasy, maintainable, no awkward decisions to remember and document.\nShe keeps it simple.\n\n** Chaining\n:PROPERTIES:\n:CUSTOM_ID: chaining\n:END:\n\nThe result of an aggregation may become the source of further\nprocessing.  To do that, just add a =#+NAME:= or =#+TBLNAME:= line\njust above the aggregated table.  Here is an example of a double\naggregation:\n\n#+begin_example\n#+NAME: squantity\n#+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\"\n| Day       | SQuantity |\n|-----------+-----------|\n| Monday    |        14 |\n| Tuesday   |        45 |\n| Wednesday |        54 |\n| Thursday  |        83 |\n| Friday    |        22 |\n#+TBLFM: @1$2=SQuantity\n#+END:\n\n#+BEGIN: aggregate :table \"squantity\" :cols \"vsum(SQuantity)\"\n| vsum(SQuantity) |\n|-----------------|\n|             218 |\n#+END:\n#+end_example\n\nNote the spreadsheet cell formula =@1$2=SQuantity=, which changes the\ncolumn heading from it default =vsum(Quantity)= to =SQuantity=.  This\nnew heading will survive any refresh.\n\nSometimes the name of the aggregated table is not found by some babel\nblock referencing it (Gnuplot blocks are among them). To fix that,\njust exchange the =#+NAME:= and =#+BEGIN:= lines:\n\n#+begin_example\n#+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\"\n#+NAME: squantity\n| Day       | SQuantity |\n|-----------+-----------|\n| Monday    |        14 |\n| Tuesday   |        45 |\n| Wednesday |        54 |\n| Thursday  |        83 |\n| Friday    |        22 |\n#+TBLFM: @1$2=SQuantity\n#+END:\n#+end_example\n\nThe =#.NAME:= line will survive when recomputing the aggregation (as\n=#.TBLFM:= line survives)\n\n* Pull & Push\n:PROPERTIES:\n:CUSTOM_ID: pull--push\n:END:\n\nTwo modes are available: /pull/ & /push/.\n\n** Pull mode\n:PROPERTIES:\n:CUSTOM_ID: pull-mode\n:END:\n\nIn the /pull/ mode, we use so called /\"dynamic blocks\"/.\nThe resulting table knows how to build itself.\n\nExample:\n\nWe have a source table which is unaware that it will be derived in an\naggregated table:\n\n#+begin_example\n#+NAME: source1\n| Day       | Color | Level | Quantity |\n|-----------+-------+-------+----------|\n| Monday    | Red   |    30 |       11 |\n| Monday    | Blue  |    25 |        3 |\n| Tuesday   | Red   |    51 |       12 |\n| Tuesday   | Red   |    45 |       15 |\n| Tuesday   | Blue  |    33 |       18 |\n| Wednesday | Red   |    27 |       23 |\n| Wednesday | Blue  |    12 |       16 |\n| Wednesday | Blue  |    15 |       15 |\n| Thursday  | Red   |    39 |       24 |\n| Thursday  | Red   |    41 |       29 |\n| Thursday  | Red   |    49 |       30 |\n| Friday    | Blue  |     7 |        5 |\n| Friday    | Blue  |     6 |        8 |\n| Friday    | Blue  |    11 |        9 |\n#+end_example\n\nWe create somewhere else a /dynamic block/ which carries the\nspecification of the aggregation:\n\n#+begin_example\n#+BEGIN: aggregate :table \"source1\" :cols \"Day vmean(Level) vsum(Quantity)\"\n| Day       | vmean(Level) | vsum(Quantity) |\n|-----------+--------------+----------------|\n| Monday    |         27.5 |             14 |\n| Tuesday   |           43 |             45 |\n| Wednesday |           18 |             54 |\n| Thursday  |           43 |             83 |\n| Friday    |            8 |             22 |\n#+END\n#+end_example\n\nTyping =C-c C-c= in the dynamic block recomputes it freshly.\n\n** Push mode\n:PROPERTIES:\n:CUSTOM_ID: push-mode\n:END:\n\nIn /push/ mode, the source table drives the creation of derived\ntables. We specify the wanted results in =#+ORGTBL: SEND= directives\n(as many as desired):\n\n#+begin_example\n#+ORGTBL: SEND derived1 orgtbl-to-aggregated-table :cols \"vmean(Level) vsum(Quantity)\"\n#+ORGTBL: SEND derived2 orgtbl-to-aggregated-table :cols \"Day vmean(Level) vsum(Quantity)\"\n| Day       | Color | Level | Quantity |\n|-----------+-------+-------+----------|\n| Monday    | Red   |    30 |       11 |\n| Monday    | Blue  |    25 |        3 |\n| Tuesday   | Red   |    51 |       12 |\n| Tuesday   | Red   |    45 |       15 |\n| Tuesday   | Blue  |    33 |       18 |\n| Wednesday | Red   |    27 |       23 |\n| Wednesday | Blue  |    12 |       16 |\n| Wednesday | Blue  |    15 |       15 |\n| Thursday  | Red   |    39 |       24 |\n| Thursday  | Red   |    41 |       29 |\n| Thursday  | Red   |    49 |       30 |\n| Friday    | Blue  |     7 |        5 |\n| Friday    | Blue  |     6 |        8 |\n| Friday    | Blue  |    11 |        9 |\n#+end_example\n\nWe must create the receiving blocks somewhere else in the same file:\n\n#+begin_example\n#+BEGIN RECEIVE ORGTBL derived1\n#+END RECEIVE ORGTBL derived1\n#+end_example\n\n#+begin_example\n#+BEGIN RECEIVE ORGTBL derived2\n#+END RECEIVE ORGTBL derived2\n#+end_example\n\nThen we come back to the source table and type =C-c C-c= with the\ncursor on the 1st pipe of the table, to refresh the derived tables:\n\n#+begin_example\n#+BEGIN RECEIVE ORGTBL derived1\n|  vmean(Level) | vsum(Quantity) |\n|---------------+----------------|\n| 27.9285714286 |            218 |\n#+END RECEIVE ORGTBL derived1\n#+end_example\n\n#+begin_example\n#+BEGIN RECEIVE ORGTBL derived2\n| Day       | vmean(Level) | vsum(Quantity) |\n|-----------+--------------+----------------|\n| Monday    |         27.5 |             14 |\n| Tuesday   |           43 |             45 |\n| Wednesday |           18 |             54 |\n| Thursday  |           43 |             83 |\n| Friday    |            8 |             22 |\n#+END RECEIVE ORGTBL derived2\n#+end_example\n\n** Pull or push ?\n:PROPERTIES:\n:CUSTOM_ID: pull-or-push-\n:END:\n\nPull & push modes use the same engine in the background.\nThus, using either is just a matter of convenience.\n\nPull mode is the most straightforward. Also the wizard operates on the\npull mode only. Almost all examples in this documentation are in pull\nmode. If you cannot decide, just use the pull mode.\n\n_Glitch:_ in push mode you may see strange output like =\\_{}=.\nThis is an escape generated by Org Mode (nothing to do with OrgAggregate).\nIt happens for the following characters: =&%#_^=\nTo disable that, in the =#+ORGTBL: SEND= line, add this parameter:\n=:no-escape true=\n\n* Debugging\n:PROPERTIES:\n:CUSTOM_ID: debugging\n:END:\nThe work of OrgAggregate is to hand out pieces of the input table to\nCalc (the Emacs calculator).\n\nIs some intricate cases, it may be useful to see what is going on. The\ndebugging formatters come handy.\n\n** Seeing the $ forms\n:PROPERTIES:\n:CUSTOM_ID: seeing-the--forms\n:END:\n\nHere is an example input table:\n\n#+begin_example\n#+name: inputdebug\n|   nn | aa |\n|------+----|\n| 1.23 | a  |\n| 7.65 | b  |\n| 8.46 | c  |\n|------+----|\n| 2.44 | d  |\n| 6.81 | e  |\n#+end_example\n\nAnd here is an aggregation to debug:\n\n#+begin_example\n#+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10) vsum(aa+7)\"\n| hline | vsum(nn*10) | vsum(aa+7)     |\n|-------+-------------+----------------|\n|     0 |       173.4 | a + b + c + 21 |\n|     1 |        92.5 | d + e + 14     |\n#+END:\n#+end_example\n\nSo far so good. But we would like to know what Calc did. To do so let\nus add the =c= formatter.\n\n#+begin_example\n#+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10);c vsum(aa+7);c\"\n| hline | vsum(nn*10) | vsum(aa+7) |\n|-------+-------------+------------|\n|     0 | vsum($1*10) | vsum($2+7) |\n|     1 | vsum($1*10) | vsum($2+7) |\n#+END:\n#+end_example\n\nEach output cell now contains the formula, with column names replaced\nby dollar equivalent forms. But without any further processing.\n\n** Seeing Calc formulas before evaluation\n:PROPERTIES:\n:CUSTOM_ID: seeing-calc-formulas-before-evaluation\n:END:\n\nLet us go one step further with the =C= formatter:\n\n#+begin_example\n#+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10);C vsum(aa+7);C\"\n| hline | vsum(nn*10)                 | vsum(aa+7)          |\n|-------+-----------------------------+---------------------|\n|     0 | vsum([1.23, 7.65, 8.46] 10) | vsum([a, b, c] + 7) |\n|     1 | vsum([2.44, 6.81] 10)       | vsum([d, e] + 7)    |\n#+END:\n#+end_example\n\nThe dollar forms were replaced by Calc vectors made of input cells. No\nfoldings or simplifications went on. The vectors are slices of columns,\nselected by OrgAggregate in response of the =hline= aggregation.\n\nWe see that multiplying by =10= or adding =7= is done on a Calc vector. It\nhappens that Calc knows how to multiply or add something to a\nvector. OrgAggregate does not perform those operations, it delegates\nthem to Calc.\n\n** Seeing Lisp internal form of Calc formulas\n:PROPERTIES:\n:CUSTOM_ID: seeing-lisp-internal-form-of-calc-formulas\n:END:\n\nWe can also view the same results, formatted as Lisp forms (rather\nthan Calc forms) with the =Q= formatter:\n\n#+begin_example\n#+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10);Q vsum(aa+7);Q\"\n| hline | vsum(nn*10)                                                               | vsum(aa+7)                                                            |\n|-------+---------------------------------------------------------------------------+-----------------------------------------------------------------------|\n|     0 | (calcFunc-vsum (* (vec (float 123 -2) (float 765 -2) (float 846 -2)) 10)) | (calcFunc-vsum (+ (vec (var a var-a) (var b var-b) (var c var-c)) 7)) |\n|     1 | (calcFunc-vsum (* (vec (float 244 -2) (float 681 -2)) 10))                | (calcFunc-vsum (+ (vec (var d var-d) (var e var-e)) 7))               |\n#+END:\n#+end_example\n\nThis is the internal, Lisp representation of Calc formulas.\n\n** Example of debugging vsum(nn^2)\n:PROPERTIES:\n:CUSTOM_ID: example-of-debugging-vsumnn2\n:END:\n\nBeware of a formula like =vsum(nn^2)=. This gives the expected result,\nalthough not in the obvious way. Let us see what happens, thanks to\nthe =C= debugging formatter:\n\n#+begin_example\n#+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn^2);C\"\n| hline | vsum(nn^2)                 |\n|-------+----------------------------|\n|     0 | vsum([1.23, 7.65, 8.46]^2) |\n|     1 | vsum([2.44, 6.81]^2)       |\n#+END:\n#+end_example\n\nWe are not summing squares. We are squaring Calc vectors. Calc being a\nmathematical tool, it interprets the product of two vectors as the sum\nof the products element-wise, as a mathematician would do. Then =vsum=\nis applied on a single resulting value. So =vsum= is useless in this\ncase. That can be confirmed:\n\n#+begin_example\n#+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn^2) nn^2 vprod(nn^2)\"\n| hline | vsum(nn^2) |    nn^2 | vprod(nn^2) |\n|-------+------------+---------+-------------|\n|     0 |    131.607 | 131.607 |     131.607 |\n|     1 |    52.3297 | 52.3297 |     52.3297 |\n#+END:\n#+end_example\n\nTherefore, changing =vsum= to =vprod= does not change the result. This can\nbe unexpected.\n\n** Summary of debugging formatters\n:PROPERTIES:\n:CUSTOM_ID: summary-of-debugging-formatters\n:END:\n\nTo summarize the debugging settings:\n- =c=: output Calc formula\n- =C=: output Calc formula with dollar forms substituted by actual input data\n- =q=: output Lisp formula\n- =Q=: output Lisp formula with column forms substituted by actual input data\n\n* Tricks\n:PROPERTIES:\n:CUSTOM_ID: tricks\n:END:\nThis chapter collects some tricks that may be useful.\n\n** Sorting\n:PROPERTIES:\n:CUSTOM_ID: sorting-0\n:END:\n#+begin_example\n#+name: trick_table_1\n| column |\n|--------|\n|    677 |\n|    713 |\n|    459 |\n|    537 |\n|    881 |\n#+end_example\n\nWhen several cells of a column need to be sorted, the Calc =calc-sort()= function is handy:\n\n#+begin_example\n#+BEGIN: aggregate :table \"trick_table_1\" :cols \"(column) sort(column)\"\n| (column)                  | sort(column)              |\n|---------------------------+---------------------------|\n| [677, 713, 459, 537, 881] | [459, 537, 677, 713, 881] |\n#+END:\n#+end_example\n\n- =(column)= gives the list of values to aggregate, without aggregating them.\n- =sort(column)= gives the same list sorted in ascending order.\n\n** A few lowest or highest values\n:PROPERTIES:\n:CUSTOM_ID: a-few-lowest-or-highest-values\n:END:\n\nUsed with =subvec()=, =sort()= can retrieve the two lowest or the two\nhighest values:\n\n#+begin_example\n#+BEGIN: aggregate :table \"trick_table_1\" :cols \"subvec(sort(column),1,3) subvec(sort(column),count()-1)\"\n| subvec(sort(column),1,3) | subvec(sort(column),count()-1) |\n|--------------------------+--------------------------------|\n| [459, 537]               | [713, 881]                     |\n#+END:\n#+end_example\n\n- =subvec(...,1,3)= extracts the two first values: from =1= to =3= excluded.\n- =subvec(...,count()-1)= extracts the two last values, numbered\n  =count()-1= and =count()=\n\nAnd of course we may retrieve the average of the two first and the two\nlast values:\n\n#+begin_example\n#+BEGIN: aggregate :table \"trick_table_1\" :cols \"vmean(subvec(sort(column),1,3)) vmean(subvec(sort(column),count()-1))\"\n| vmean(subvec(sort(column),1,3)) | vmean(subvec(sort(column),count()-1)) |\n|---------------------------------+---------------------------------------|\n|                             498 |                                   797 |\n#+END:\n#+end_example\n\n** Span of values\n:PROPERTIES:\n:CUSTOM_ID: span-of-values\n:END:\n\n=vmin()= and =vmax()= can compute the span of aggregated values:\n\n#+begin_example\n#+BEGIN: aggregate :table \"trick_table_1\" :cols \"vmin(column) vmax(column) vmax(column)-vmin(column)\"\n| vmin(column) | vmax(column) | vmax(column)-vmin(column) |\n|--------------+--------------+---------------------------|\n|          459 |          881 |                       422 |\n#+END:\n#+end_example\n\n** No aggregation\n:PROPERTIES:\n:CUSTOM_ID: no-aggregation\n:END:\nWhy would one want to use OrgAggregate while not aggregating? To\nbenefit from the other features of OrgAggregate:\n- column rearrangement\n- sorting\n- formatting\n- =#+TBLFM= survival\n- row filtering\n- preprocess\n- postprocess\n\nTo do so, mention the virtual column =@#= in =:cols= and make it invisible\nwith =;<>=. As =@#= is different for each row, the aggregation will\nconsider each row as a separate group. Therefore, no aggregation on\nanother column will do anything more.\n\nFor example, here we:\n- put =Color= as the first column (it is the second in the input),\n- ignore the =Day= column,\n- sort by =Level=,\n- compute =Quantity/7=,\n- format it with 2 digits after dot.\n\n#+begin_example\n#+BEGIN: aggregate :table \"original\" :cols \"@#;<> Color Level;^n vmax(Quantity/7);'Q10';f2\"\n| Color | Level |  Q10 |\n|-------+-------+------|\n| Blue  |     6 | 1.14 |\n| Blue  |     7 | 0.71 |\n| Blue  |    11 | 1.29 |\n| Blue  |    12 | 2.29 |\n| Blue  |    15 | 2.14 |\n| Blue  |    25 | 0.43 |\n| Red   |    27 | 3.29 |\n| Red   |    30 | 1.57 |\n| Blue  |    33 | 2.57 |\n| Red   |    39 | 3.43 |\n| Red   |    41 | 4.14 |\n| Red   |    45 | 2.14 |\n| Red   |    49 | 4.29 |\n| Red   |    51 | 1.71 |\n#+END:\n#+end_example\n\nWe used the =vmax()= aggregating function on =Quantity/7=, because\notherwise we would get a vector with a single value. As there is a\nsingle value, any aggregating function will do the trick: =vmin()=,\n=head()=, =rtail()=, =vsum()=, =vprod()=, =vmean()=, =vgmean()=, =vhmean()=, =vspan()=,\n=vmedian()=.\n\n* Installation\n:PROPERTIES:\n:CUSTOM_ID: installation\n:END:\n\nEmacs package on Melpa: add the following lines to your =.emacs= file,\nand reload it.\n\n#+begin_example\n(add-to-list 'package-archives '(\"melpa\" . \"http://melpa.org/packages/\") t)\n(package-initialize)\n#+end_example\n\nYou may also customize this variable:\n#+begin_example\nM-x customize-variable package-archives\n#+end_example\n\nThen browse the list of available packages and install =orgtbl-aggregate=\n#+begin_example\nM-x package-list-packages\n#+end_example\n\nAlternatively, you can download the lisp file, and load it:\n\n#+begin_example\n(load-file \"orgtbl-aggregate.el\")\n#+end_example\n\n* Authors, contributors\n:PROPERTIES:\n:CUSTOM_ID: authors-contributors\n:END:\n\nAuthors\n- Thierry Banel, tbanelwebmin at free dot fr, inception & implementation.\n- Michael Brand, Calc unleashed, =#+TBLFM= survival, empty input cells, formatters.\n\nContributors\n- Eric Abrahamsen, non-ASCII column names\n- Alejandro Erickson, quoting non alphanumeric column names\n- Uwe Brauer, simpler example in documentation, take\n  org-calc-default-modes preferences into account\n- Peking Duck, fixed obsolete letf function\n- Bill Hunker, discovered =\\_{}= escape\n- Dirk Schmitt, surviving =#.NAME:= line\n- Dale Sedivec, case insensitive =#+NAME:= tags\n- falloutphil, underscore in column names\n- Baudilio Tejerina, t, T, U formatters\n- Marco Pas, bug comparing empty string\n- wuqui, sorting output table, filtering only\n- Nicolas Viviani, output hlines\n- Nils Lehmann, support old versions of the rx library\n- Shankar Rao, =:post= post-processing\n- Misohena (https://misohena.jp/blog/author/misohena),\n  double width Japanese characters (string-width vs. length)\n- Kevin Brubeck Unhammer, ignore formatting cookies\n- Tilmann Singer, more flexibility in duration format\n- Piotr Panasiuk, =#+CAPTION:= and any tags survive\n- Luis Miguel Hernanz, fix regex bug\n- Jason Hemann, output column names no longer have quotes\n- Tilmann Singer, computed aggregating bins, =\"month(Date)\"= in his use\n  case\n\n* Changes\n:PROPERTIES:\n:CUSTOM_ID: changes\n:END:\nTop: earliest change. Bottom: latest change.\n\n- Wizard now correctly asks for columns with =$1, $2...= names\n  when table header is missing\n- Handle tables beginning with hlines\n- Handle non-ASCII column names\n- =:formula= parameter and =#+TBLFM= survival\n- Empty cells are ignored.\n- Empty output upon too small input set\n- Fix ordering of output values\n- Aggregations formulas may now be arbitrary expressions\n- Table headers (and the lack of) are better handled\n- Modifiers and formatters can now be specified as in the spreadsheet\n- Aggregation function names can optionally have a leading =v=, like =sum= & =vsum=\n- Increased performance on large data sets\n- Tables can be named with =#+NAME:= besides =#+TBLNAME:=\n- Document Melpa installation\n- Support quoting of column names, like \"a.b\" or 'c/d'\n- Disable =\\_{}= escape\n- =#+NAME:= inside =#+BEGIN:= survives\n- Missing input cells handled as empty ones\n- Back-port Org Mode =9.4= speed up\n- Increase performance when inserting result into the buffer\n- Aligned output in push mode\n- Added a hash-table to speedup aggregation\n- Back-port org-table-to-lisp which is now much faster\n- =vlist(X)= now yields input cells verbatim were =(X)= yields Calc processed input cells\n- Document dates handling and the =date()= function\n- Implement =HH:MM:SS= durations and =T=, =t=, =U= formatters\n- Sort output\n- Create hlines in the output\n- Missing :cond parameter means all columns\n- Remove =C-c C-x i=, use standard =C-c C-x x= instead\n- Avoid name collision between Calc functions and columns\n- More readable & faster code\n- Support for old versions of the rx library\n- =:post= post-processing\n- Propagate multiple rows source header to the aggregated header\n- Ignore data rows containing formatting cookies\n- Follow Org Mode way of handling Calc settings in Lisp code\n- Hours in durations are no longer restricted to 2 digits\n- 3x speedup =org-table-to-lisp= and avoid Emacs 27 to 30 incompatibilities\n- =#+CAPTION:= and any other tag survive inside =#+BEGIN:=\n- Output column names are now stripped from quotes, better reflecting\n  input names.\n- Table-of-contents in README.org (thanks org-make-toc)\n- Add formatters =c= =C= =q= =Q= (useful for debugging or understanding\n  OrgAggregate)\n- Formulas involving =hline= like =vmean(hline*10)= are now taken into\n  account\n- Documentation is now integrated right into Emacs in the =info= format.\n  Type =M-: (info \"orgtbl-aggregate\")=\n- Input table may now be the result of a Babel script (virtual table).\n- Better handling of user errors in the =:post= directive.\n- Speedup of resulting table recalculation when there are formulas in\n  =#+tblfm:= or in =:formula=. The overall aggregation may be up to x6\n  faster and ÷5 less memory hungry.\n- Circumvent an Org Mode bug in case there are a column-formula along\n  with a cell-formula, the cell-one not being calculated. (Bonus: 15%\n  speedup).\n- Fix issue #24: bug in date parsing.\n- Virtual pre-computed input columns.\n- Better explanation of the input table reference syntax, including\n  distant tables and virtual table produced by Babel blocks.\n- Support for CSV and JSON formatted input tables.\n- New =@#= virtual column giving the number of each row, pretty much\n  like the Org table spreadsheet =@#= virtual column.\n- Header and column names can be specified for CSV input tables, as\n  well as horizontal separators (=hline=).\n- Aggregation of the titles of this README.\n- New free-form wizard.\n- Illustrate README with Uniline graphics.\n- JSON and CSV input tables can now live inside Org Mode blocks.\n- Example for computing a refreshable grand total.\n- Document intervals and error-forms handling.\n- Special columns =@#= and =hline= are handled by transpose.\n\n* GPL 3 License\n:PROPERTIES:\n:CUSTOM_ID: gpl-3-license\n:END:\nCopyright (C) 2013-2026  Thierry Banel\n\norgtbl-aggregate is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\norgtbl-aggregate is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "orgtbl-aggregate.el",
    "content": ";;; orgtbl-aggregate.el --- Aggregate an Org Mode table | + | + | into another table  -*- coding:utf-8; lexical-binding: t;-*-\n;; Copyright (C) 2013-2026  Thierry Banel\n\n;; Authors:\n;;   Thierry Banel tbanelwebmin at free dot fr\n;;   Michael Brand michael dot ch dot brand at gmail dot com\n;; Contributors:\n;;   Eric Abrahamsen, Alejandro Erickson Uwe Brauer, Peking Duck, Bill\n;;   Hunker, Dirk Schmitt, Dale Sedivec, falloutphil, Baudilio\n;;   Tejerina, Marco Pas, wuqui, Nicolas Viviani, Nils Lehmann,\n;;   Shankar Rao, Misohena, Kevin Brubeck Unhammer, Tilmann Singer,\n;;   Piotr Panasiuk, Luis Miguel Hernanz, Jason Hemann\n\n;; Package-Requires: ((emacs \"26.1\"))\n\n;; Version: 1.0\n;; Keywords: data, extensions\n;; URL: https://github.com/tbanel/orgaggregate/blob/master/README.org\n\n;; orgtbl-aggregate is free software: you can redistribute it and/or modify\n;; it under the terms of the GNU General Public License as published by\n;; the Free Software Foundation, either version 3 of the License, or\n;; (at your option) any later version.\n\n;; orgtbl-aggregate is distributed in the hope that it will be useful,\n;; but WITHOUT ANY WARRANTY; without even the implied warranty of\n;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n;; GNU General Public License for more details.\n\n;; You should have received a copy of the GNU General Public License\n;; along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n;;; Commentary:\n;;\n;; A new org-mode table is automatically updated,\n;; based on another table acting as a data source\n;; and user-given specifications for how to perform aggregation.\n;;\n;; Example:\n;; Starting from a source table of activities and quantities\n;; (whatever they are) over several days,\n;;\n;; #+TBLNAME: original\n;; | Day       | Color | Level | Quantity |\n;; |-----------+-------+-------+----------|\n;; | Monday    | Red   |    30 |       11 |\n;; | Monday    | Blue  |    25 |        3 |\n;; | Tuesday   | Red   |    51 |       12 |\n;; | Tuesday   | Red   |    45 |       15 |\n;; | Tuesday   | Blue  |    33 |       18 |\n;; | Wednesday | Red   |    27 |       23 |\n;; | Wednesday | Blue  |    12 |       16 |\n;; | Wednesday | Blue  |    15 |       15 |\n;; | Thursday  | Red   |    39 |       24 |\n;; | Thursday  | Red   |    41 |       29 |\n;; | Thursday  | Red   |    49 |       30 |\n;; | Friday    | Blue  |     7 |        5 |\n;; | Friday    | Blue  |     6 |        8 |\n;; | Friday    | Blue  |    11 |        9 |\n;;\n;; an aggregation is built for each day (because several rows\n;; exist for each day), typing C-c C-c\n;;\n;; #+BEGIN: aggregate :table original :cols \"Day mean(Level) sum(Quantity)\"\n;; | Day       | mean(Level) | sum(Quantity) |\n;; |-----------+-------------+---------------|\n;; | Monday    |        27.5 |            14 |\n;; | Tuesday   |          43 |            45 |\n;; | Wednesday |          18 |            54 |\n;; | Thursday  |          43 |            83 |\n;; | Friday    |           8 |            22 |\n;; #+END\n;;\n;; A wizard can be used:\n;; C-c C-x x aggregate\n;;\n;; Full documentation here:\n;;   https://github.com/tbanel/orgaggregate/blob/master/README.org\n\n;;; Requires:\n(require 'calc-ext)\n(require 'calc-aent)\n(require 'calc-alg)\n(require 'calc-arith)\n(require 'org)\n(require 'org-table)\n(require 'org-id)\n(require 'thingatpt) ;; just for thing-at-point--read-from-whole-string\n(eval-when-compile (require 'cl-lib))\n(require 'rx)\n(require 'json)\n(eval-when-compile\n  (cl-proclaim '(optimize (speed 3) (safety 0))))\n\n;;; Code:\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; The venerable Calc is used thoroughly by the Aggregate package.\n;; A few bugs were found.\n;; They have been fixed in recent versions of Emacs\n;; Uncomment the fixes if needed\n;(defun math-max-list (a b)\n;  (if b\n;      (if (or (Math-anglep (car b)) (eq (caar b) 'date)\n;\t      (and (eq (car (car b)) 'intv) (math-intv-constp (car b)))\n;\t      (math-infinitep (car b)))\n;\t  (math-max-list (math-max a (car b)) (cdr b))\n;\t(math-reject-arg (car b) 'anglep))\n;    a))\n;\n;(defun math-min-list (a b)\n;  (if b\n;      (if (or (Math-anglep (car b)) (eq (caar b) 'date)\n;\t      (and (eq (car (car b)) 'intv) (math-intv-constp (car b)))\n;\t      (math-infinitep (car b)))\n;\t  (math-min-list (math-min a (car b)) (cdr b))\n;\t(math-reject-arg (car b) 'anglep))\n;    a))\n;; End of Calc fixes\n\n;; [bazilo synchronize orgtbl-αggregate & orgtbl-joιn\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; creating long lists in the right order may be done\n;; - by (nconc)  but behavior is quadratic\n;; - by (cons) (nreverse)\n;; a third way involves keeping track of the last cons of the growing list\n;; a cons at the head of the list is used for housekeeping\n;; the actual list is (cdr ls)\n;;\n;; A list with 4 elements:\n;; ╭─┬─╮ ╭────┬─╮ ╭────┬─╮ ╭────┬─╮ ╭────┬─╮\n;; │◦│◦┼▶┤val1│◦┼▶┤val2│◦┼▶┤val3│◦┼▶┤val4│◦┼▶╴nil\n;; ╰┼┴─╯ ╰────┴─╯ ╰────┴─╯ ╰────┴─╯ ╰─┬──┴─╯\n;;  │                                 ▲\n;;  ╰─────────────────────────────────╯\n;;\n;; A newly created, empty list\n;; ╭─┬─╮\n;; │◦│◦┼▶─nil\n;; ╰┼┴┬╯\n;;  │ ▲\n;;  ╰─╯\n\n(eval-when-compile\n\n  (defmacro orgtbl-aggregate--list-create ()\n    \"Create an appendable list.\"\n    `(let ((x (cons nil nil)))\n       (setcar x x)))\n\n  (defmacro orgtbl-aggregate--list-append (ls value)\n    \"Append VALUE at the end of LS in O(1) time.\"\n    `(setcar ,ls (setcdr (car ,ls) (cons ,value nil))))\n\n  (defmacro orgtbl-aggregate--list-get (ls)\n    \"Return the regular Lisp list from LS.\"\n    `(cdr ,ls))\n\n  (defmacro orgtbl-aggregate--pop-simple (place)\n    \"Like (pop PLACE), but without returning (car PLACE).\"\n    `(setq ,place (cdr ,place)))\n\n  (defmacro orgtbl-aggregate--pop-leading-hline (table)\n    \"Remove leading hlines from TABLE, if any.\"\n    `(while (not (listp (car ,table)))\n       (orgtbl-aggregate--pop-simple ,table)))\n\n  (defmacro orgtbl-aggregate--string-match-p (regexp string &optional start)\n    \"Same as standard `string-match-p'\nbut written as a defmacro instead of a defsubst,\nwhich saves 4 or 5 byte-codes at each call.\"\n    `(string-match ,regexp ,string ,start t))\n  )\n\n(defun orgtbl-aggregate--alist-get-remove (key alist)\n  \"A variant of alist-get which removes an entry once read.\nALIST is a list of pairs (key . value).\nSearch ALIST for a KEY. If found, replace the key in (key . value)\nby nil, and return value. If nothing is found, return nil.\"\n  (let ((x (assq key alist)))\n    (when x\n      (setcar x nil)\n      (cdr x))))\n\n(defun orgtbl-aggregate--plist-get-remove (params prop)\n  \"Like `plist-get', but also remove PROP from PARAMS.\"\n  (let ((v (plist-get params prop)))\n    (if v\n        (setcar (memq prop params) nil))\n    v))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; The function (org-table-to-lisp) have been greatly enhanced\n;; in Org Mode version 9.4\n;; To benefit from this speedup in older versions of Org Mode,\n;; this function is copied here with a slightly different name\n;; It has also undergone near 3x speedup,\n;; - by not using regexps\n;; - achieving the shortest bytecode\n;; Furthermore, this version avoids the\n;; inhibit-changing-match-data and looking-at\n;; incompatibilities between Emacs-27 and Emacs-30\n\n(defun orgtbl-aggregate--table-to-lisp (&optional txt)\n  \"Convert the table at point to a Lisp structure.\nThe structure will be a list.  Each item is either the symbol `hline'\nfor a horizontal separator line, or a list of field values as strings.\nThe table is taken from the parameter TXT, or from the buffer at point.\"\n  (if txt\n      (with-temp-buffer\n\t(buffer-disable-undo)\n        (insert txt)\n        (goto-char (point-min))\n        (orgtbl-aggregate--table-to-lisp))\n    (save-excursion\n      (goto-char (org-table-begin))\n      (let (table)\n        (while (progn (skip-chars-forward \" \\t\")\n                      (eq (following-char) ?|))\n\t  (forward-char)\n\t  (push\n\t   (if (eq (following-char) ?-)\n\t       'hline\n\t     (let (row)\n\t       (while (progn (skip-chars-forward \" \\t\")\n                             (not (eolp)))\n                 (let ((q (point)))\n                   (skip-chars-forward \"^|\\n\")\n                   (goto-char\n                    (let ((p (point)))\n                      (unless (eolp) (setq p (1+ p)))\n                      (skip-chars-backward \" \\t\" q)\n                      (push\n                       (buffer-substring-no-properties q (point))\n                       row)\n                      p))))\n\t       (nreverse row)))\n\t   table)\n\t  (forward-line))\n\t(nreverse table)))))\n\n;; There is no CSV parser bundled with Emacs. In order to avoid a\n;; dependency on a package, here is an implementation of a parser.  It\n;; is made of the same technology as `orgtbl-aggregate--table-to-lisp'\n;; (which is now integrated into the newest versions of Emacs). It is\n;; probably as fast as can be in Emacs-Lisp byte-code.\n\n(defun orgtbl-aggregate--csv-to-lisp (header colnames)\n  \"Convert current buffer in CSV to Lisp.\nIt recognize cells protected by double quotes, and cells not protected.\nWhen a cell is not protected, blanks are kept.\nWhen a cell is protected, blanks before the first double quote are ignored.\nDouble double quotes are recognized within a cell double-quoted.\nThe last line may or may not end in a newline.\nSeparators are comma, semicolon, or TAB. They can be mixed.\nIf a row is empty, it is considered as a separator, and translated\nto `hline', the Org table horizontal separator.\nHEADER non nil means that the first row must be interpreted as a header.\nCOLNAMES, if not nil, is a list of column names.\"\n  (goto-char (point-min))\n  (let (table)\n    (while (not (eobp))\n      (let (row)\n        (while (not (eolp))\n          (let ((p (point)))\n            (skip-chars-forward \" \")\n            (if (eq (following-char) ?\\\")\n                (let (dquote)\n                  (forward-char 1)\n                  (setq p (point))\n                  (while\n                      (progn\n                        (skip-chars-forward \"^\\\"\")\n                        (forward-char 1)\n                        (if (eq (following-char) ?\\\")\n                            (progn (forward-char 1)\n                                   (setq dquote t)))))\n                  (push\n                   (let ((cell\n                          (buffer-substring-no-properties p (1- (point)))))\n                     (if dquote\n                         (string-replace \"\\\"\\\"\" \"\\\"\" cell)\n                       cell))\n                   row)\n                  (skip-chars-forward \" \"))\n              (skip-chars-forward \"^,;\\t\\n\")\n              (push\n               (buffer-substring-no-properties p (point))\n               row))\n            (skip-chars-forward \",;\\t\" (1+ (point)))))\n        (push\n         (if row (nreverse row) 'hline)\n         table)\n        (or (eobp) (forward-char 1))))\n    (setq table (nreverse table))\n    (if header\n        (setcdr table (cons 'hline (cdr table))))\n    (if colnames\n        (setq table (cons colnames (cons 'hline table))))\n    table))\n\n;; A few rx abbreviations\n;; each time a bit of a regexp is used twice or more,\n;; it makes sense to define an abbrev\n\n(eval-when-compile ;; not used at runtime\n\n  ;; search for table name, such as:\n  ;; #+tablename: mytable\n  (rx-define tblname\n    (seq bol (* blank) \"#+\" (? \"tbl\") \"name:\" (* blank)))\n\n  ;; skip lines beginning with # in order to reach the start of table\n  (rx-define skip-meta-table (firstchars)\n    (seq\n     (0+ (0+ blank) (? firstchars (0+ nonl)) \"\\n\")\n     (0+ blank) \"|\"))\n\n  ;; just to get ride of a few parenthesis\n  (rx-define notany (&rest list)\n    (not (any list)))\n\n  ;; match quoted column names, like\n  ;; 'col a' \"col b\" colc\n  (rx-define quotedcolname (&rest bare)\n    (or\n     (seq ?'  (* (notany ?' )) ?' )\n     (seq ?\\\" (* (notany ?\\\")) ?\\\")\n     bare))\n\n  ;; match a column name not protected by quotes\n  (rx-define nakedname\n    (+ (any \"$._#@\" word)))\n  )\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Here is a bunch of useful utilities,\n;; generic enough to be detached from the orgtbl-aggregate package.\n;; For the time being, they are here.\n\n(defun orgtbl-aggregate--list-local-tables (file)\n  \"Search for available tables in FILE.\nIf FILE is nil, use current buffer.\"\n  (interactive)\n  (with-current-buffer\n      (if file (find-file-noselect file) (current-buffer))\n    (save-excursion\n      (goto-char (point-min))\n      (let ((case-fold-search t))\n        (cl-loop\n         while\n         (re-search-forward\n          (rx tblname (group (*? nonl)) (* blank) eol)\n          nil t)\n         collect (match-string-no-properties 1))))))\n\n(defun orgtbl-aggregate--table-from-babel (name-or-id)\n  \"Retrieve an input table as the result of running a Babel block.\nNAME-OR-ID is the usual Org convention for pointing to a distant reference.\nExamples: babel, file:babel, file:babel[1:3,2:5], file:babel(p1=…,p2=…)\nThis function could work also for a table,\nbut this has already been short-circuited.\"\n  ;; A user error is generated in case no Babel block is found\n  (let ((table (org-babel-ref-resolve name-or-id)))\n    (and\n     table\n     (consp table)\n     (or (eq (car table) 'hline)\n         (consp (car table)))\n     table)))\n\n(defun orgtbl-aggregate--block-from-name (file name)\n  \"Parse an Org table named NAME in a distant Org file named FILE.\nFILE is a filename with possible relative or absolute path.\nIf FILE is nil, look in the current buffer.\"\n  (with-current-buffer\n      (if file\n          (find-file-noselect file)\n        (current-buffer))\n    (save-excursion\n      (goto-char (point-min))\n      (let ((case-fold-search t))\n        (if (re-search-forward\n             (rx ;; a single regexp :)\n              tblname (literal name) (* blank) \"\\n\"\n              (0+ blank) \"#+begin\" (0+ nonl) \"\\n\"\n              (group (*? (or any \"\\n\")))\n              bol (* space) \"#+end\")\n             nil t)\n            (match-string-no-properties 1))))))\n\n(defun orgtbl-aggregate--table-from-csv (file name params)\n  \"Parse a CSV formatted table located in FILE.\nIf NAME is nil, then FILE is supposed to contain just one CSV table.\nIf NAME is given, it is supposed to be an Org block name which contains\na CSV table.\nThe cell-separator is currently guessed.\nCurrently, there is no header.\"\n  (let ((header) (colnames) (block))\n    (cl-loop\n     for p on (cdr (read params))\n     do\n     (cond\n      ((eq (car p) 'header)\n       (setq header t))\n      ((eq (car p) 'colnames)\n       (setq p (cdr p))\n       (setq colnames (car p)))\n      (t\n       (message \"parameter %S not recognized\" (car p)))))\n    (if name\n        (setq block (orgtbl-aggregate--block-from-name file name)))\n    (with-temp-buffer\n      (if name\n          (insert block)\n        (insert-file-contents file))\n      (orgtbl-aggregate--csv-to-lisp header colnames))))\n\n(defun orgtbl-aggregate--table-from-json (file name _params)\n  \"Parse a JSON formatted table located in FILE.\nFILE is a filename with possible relative or absolute path.\nCurrently, the accepted format is\n[[\\\"COL1\\\",\\\"COL2\\\",…]\n \\\"hline\\\"\n [\\\"VAL1\\\",\\\"VAL2\\\",…]\n {\\\"key1\\\":\\\"val1\\\", \\\"key2\\\":\\\"val2\\\",…},\n …\n]\nNumbers do not need to be quoted.\nHorizontal lines may be: \\\"hline\\\", null, [], {}.\nA mixture of vector and hash-objects is allowed.\nTherfore the styles vector-of-vectors and vector-of-hash-objects\nare supported.\nA header containing the column names may be given as the first row,\n(which must be a vector) followed by an horizontal line.\nKeys not found in the header (or if there is no header), are added\nto the column names.\"\n  (let ((json-object-type 'alist)\n        (json-array-type 'list)\n        (json-key-type 'string))\n    (let ((json\n           (if name\n               (json-read-from-string (orgtbl-aggregate--block-from-name file name))\n             (json-read-file file)))\n          (colnames ())\n          (result))\n      (when (and (cddr json)                ;; at least 2 rows\n                 (consp (car json))         ;; first row is a vector\n                 (not (consp (cadr json)))) ;; second row is an hline\n        (setq colnames (car json)) ;; then first row contains column names\n        (setq json (cddr json)))\n      (setq\n       result\n       (cl-loop\n        for row in json\n        if (not row)                    ;; [], {}, null\n        collect 'hline                  ;; are 'hline\n        else if (stringp row)           ;; \"symbol\"\n        collect (intern row)            ;; becomes 'symbol\n        else if (and (consp row) (consp (car row)))\n        collect                         ;; case of an hash-object\n        (progn\n          (cl-loop\n           for icell in row\n           if (and (consp icell) (not (member (car icell) colnames)))\n           do (setq colnames `(,@colnames ,(car icell))))\n          (let ((vec (make-list (length colnames) nil)))\n            (cl-loop\n             for icell in row\n             do (cl-loop\n                 for colname in colnames\n                 for ocell on vec\n                 if (equal colname (car icell))\n                 do (setcar ocell (cdr icell))))\n            vec))\n        else                            ;; case of a vector\n        collect row))\n      (if colnames\n          `(,colnames hline ,@result)\n        result))))\n\n(defun orgtbl-aggregate--table-from-name (file name)\n  \"Parse an Org table named NAME in a distant Org file named FILE.\nFILE is a filename with possible relative or absolute path.\nIf FILE is nil, look in the current buffer.\"\n  (with-current-buffer\n      (if file\n          (find-file-noselect file)\n        (current-buffer))\n    (save-excursion\n      (goto-char (point-min))\n      (and\n       (let ((case-fold-search t))\n\t (re-search-forward\n\t  (rx\n           tblname (literal name) (* blank) eol\n           (skip-meta-table \"#\"))\n\t  nil t))\n       (orgtbl-aggregate--table-to-lisp)))))\n\n(defun orgtbl-aggregate--table-from-id (id)\n  \"Parse a table following a header in a distant Org file.\nThe header have an ID property equal to ID in a PROPERTY drawer.\"\n  (let ((id-loc (org-id-find id 'marker)))\n    (when (and id-loc (markerp id-loc))\n      (with-current-buffer (marker-buffer id-loc)\n        (save-excursion\n          (goto-char (marker-position id-loc))\n          (move-marker id-loc nil)\n          (and\n           (let ((case-fold-search t))\n             (re-search-forward\n              (rx point (skip-meta-table (any \"*#:\")))\n              nil t))\n           (orgtbl-aggregate--table-to-lisp)))))))\n\n(defun orgtbl-aggregate--nil-if-empty (field)\n  (and\n   field\n   (not (string-match-p (rx bos (* blank) eos) field))\n   field))\n\n(defun orgtbl-aggregate--parse-locator (locator)\n  \"Parse LOCATOR, a description of where to find the input table.\nThe result is a vector containing:\n[\n  FILE   ; optional file where the table/Babel/CSV/JSON may be found\n  NAME   ; name of table/Babel denoted by #+name:\n  ORGID  ; Org Mode id in a property drawer (exclusive with file+name)\n  PARAMS ; optional parameters to pass to babel/CSV/JSON\n  SLICE  ; optional slicing of the resultin table, like [0:7]\n]\nIf LOCATOR looks like NAME(params…)[slice] or just NAME, then NAME\nis searched in the Org Mode database, and if found it is interpreted\nas an Org Id and put in the `orgid' field.\"\n  (unless locator (setq locator \"\"))\n  (unless\n      (string-match\n       (rx\n        bos\n        (* space)\n        (? (group-n 1 (* (notany \":\"))) \":\")\n        (* space)\n        (   group-n 2 (* (notany \"[]():\")))\n        (* space)\n        (? (group-n 3 \"(\" (* nonl) \")\"))\n        (* space)\n        (? (group-n 4 \"[\" (* nonl) \"]\"))\n        (* space)\n        eos)\n       locator)\n    (user-error \"Malformed table reference %S\" locator))\n  (let ((file   (orgtbl-aggregate--nil-if-empty (match-string 1 locator)))\n        (name   (orgtbl-aggregate--nil-if-empty (match-string 2 locator)))\n        (orgid                                                           )\n        (params (orgtbl-aggregate--nil-if-empty (match-string 3 locator)))\n        (slice  (orgtbl-aggregate--nil-if-empty (match-string 4 locator))))\n    (when (and\n           (not file)\n           (progn\n             (unless org-id-locations (org-id-locations-load))\n             (and org-id-locations\n\t          (hash-table-p org-id-locations)\n\t          (gethash name org-id-locations))))\n      (setq orgid name)\n      (setq name nil))\n    (vector file name orgid params slice)))\n\n(defun orgtbl-aggregate--assemble-locator (file name orgid params slice)\n  \"Assemble fields of a locator as a string.\nFILE NAME ORGID PARAMS SLICE are the 5 fields composing a locator.\nMany of them are optional.\nThe result is a locator suitable for orgtbl-aggregate and Org Mode.\"\n  (unless params (setq params \"\"))\n  (unless slice  (setq slice  \"\"))\n  (setq file  (orgtbl-aggregate--nil-if-empty file ))\n  (setq orgid (orgtbl-aggregate--nil-if-empty orgid))\n  (cond\n   (orgid (format \"%s%s%s\"         orgid        params slice))\n   (file  (format \"%s:%s%s%s\" file (or name \"\") params slice))\n   (t     (format \"%s%s%s\"         name         params slice))))\n\n(defun orgtbl-aggregate-table-from-any-ref (name-or-id)\n  \"Find a table referenced by NAME-OR-ID.\nThe reference is all the accepted Org references,\nand additionally pointers to CSV or JSON files.\nThe pointed to object may also be a Babel block, which when executed\nreturns an Org table. Parameters may be passed to the Babel block\nin parenthesis.\nA slicing may be applied to the table, to select rows or columns.\nThe syntax for slicing is like [1:3] or [1:3,2:5].\nReturn it as a Lisp list of lists.\nAn horizontal line is translated as the special symbol `hline'.\"\n  (unless (stringp name-or-id)\n    (setq name-or-id (format \"%s\" name-or-id)))\n  (let*\n      ((struct (orgtbl-aggregate--parse-locator name-or-id))\n       (file   (aref struct 0))\n       (name   (aref struct 1))\n       (orgid  (aref struct 2))\n       (params (aref struct 3))\n       (slice  (aref struct 4))\n       (table\n        (cond\n         ;; name-or-id = \"file:(csv …)\"\n         ((and params (string-match-p (rx bos \"(csv\") params))\n          (orgtbl-aggregate--table-from-csv file name params))\n         ;; name-or-id = \"file:(json …)\"\n         ((and params (string-match-p (rx bos \"(json\") params))\n          (orgtbl-aggregate--table-from-json file name params))\n         ;; name-or-id = \"34cbc63a-c664-471e-a620-d654b26ffa31\"\n         ;; pointing to a header in a distant org file, followed by a table\n         (orgid\n          (orgtbl-aggregate--table-from-id orgid))\n         ;; name-or-id = \"babel(p=…)\" or \"file:babel(p=…)\"\n         ((and params\n               (orgtbl-aggregate--table-from-babel\n                (if file\n                    (format \"%s:%s%s\" file name params)\n                  (format \"%s%s\" name params)))))\n         ;;name-or-id = \"table\" or \"file:table\"\n         ((orgtbl-aggregate--table-from-name file name))\n         ;; name-or-id = \"babel\" or \"file:babel\"\n         ((orgtbl-aggregate--table-from-babel\n           (if file\n               (format \"%s:%s\" file name)\n             name)))\n         ;; everything failed\n         (t\n          (user-error\n           \"Cannot find table or babel block with reference %S\"\n           name-or-id)))))\n      (if slice\n          (org-babel-ref-index-list slice table)\n        table)))\n\n(defun orgtbl-aggregate--split-string-with-quotes (string)\n  \"Like (split-string STRING), but with quote protection.\nSingle and double quotes protect space characters,\nand also single quotes protect double quotes\nand the other way around.\"\n  (let ((l (length string))\n\t(start 0)\n\t(result (orgtbl-aggregate--list-create)))\n    (save-match-data\n      (while (and (< start l)\n\t\t  (string-match\n                   (rx\n                    (* blank)\n                    (group (+ (quotedcolname (notany \" '\\\"\")))))\n\t\t   string start))\n\t(orgtbl-aggregate--list-append result (match-string 1 string))\n\t(setq start (match-end 1))))\n    (orgtbl-aggregate--list-get result)))\n\n(defun orgtbl-aggregate--merge-list-into-single-string (cols)\n  \"Create a string representing the list COLS.\nThis is the opposite of `orgtbl-aggregate--split-string-with-quotes'.\"\n  (if (listp cols)\n      (mapconcat\n       (lambda (x)\n         (format \"%s\" x)) ;; x already a string? returns it unchanged\n       cols \" \")\n    cols))\n\n(defun orgtbl-aggregate--colname-to-int (colname table &optional err)\n  \"Convert the COLNAME into an integer.\nCOLNAME is a column name of TABLE.\nThe first column is numbered 1.\nCOLNAME may be:\n- a dollar form, like $5 which is converted to 5\n- an alphanumeric name which appears in the column header (if any)\n- the special symbol `hline' which is converted into 0\nIf COLNAME is quoted (single or double quotes),\nquotes are removed beforhand.\nWhen COLNAME does not match any actual column,\nan error is generated if ERR optional parameter is true\notherwise nil is returned.\"\n  (if (symbolp colname)\n      (setq colname (symbol-name colname)))\n  (if (string-match\n       (rx\n\tbos\n\t(or\n\t (seq ?'  (group-n 1 (* (notany ?' ))) ?' )\n\t (seq ?\\\" (group-n 1 (* (notany ?\\\"))) ?\\\"))\n\teos)\n       colname)\n      (setq colname (match-string 1 colname)))\n  ;; skip first hlines if any\n  (orgtbl-aggregate--pop-leading-hline table)\n  (cond ((string= colname \"\")\n\t (and err (user-error \"Empty column name\")))\n\t((string= colname \"@#\")\n\t 0)\n\t((string= colname \"hline\")\n\t (1+ (length (car table))))\n\t((string-match (rx bos \"$\" (group (+ digit)) eos) colname)\n\t (let ((n (string-to-number (match-string 1 colname))))\n\t   (if (<= n (length (car table)))\n\t       n\n\t     (if err\n\t\t (user-error \"Column %s outside table\" colname)))))\n\t((and\n          (memq 'hline table)\n\t  (cl-loop\n\t   for h in (car table)\n\t   for i from 1\n\t   thereis (and (string= h colname) i))))\n        (err\n\t (user-error \"Column %s not found in table\" colname))))\n\n(defun orgtbl-aggregate--insert-make-spaces (n spaces-cache)\n  \"Make a string of N spaces.\nCaches results into SPACES-CACHE to avoid re-allocating\nagain and again the same string.\"\n  (if (< n (length spaces-cache))\n      (or (aref spaces-cache n)\n\t  (aset spaces-cache n (make-string n ? )))\n    (make-string n ? )))\n\n;; Time optimization: surprisingly,\n;; (insert (concat a b c)) is faster than\n;; (insert a b c)\n;; Therefore, we build the Org Mode representation of a table\n;; as a list of strings which get concatenated into a huge string.\n;; This is faster and less garbage-collector intensive than\n;; inserting cells one at a time in a buffer.\n;;\n;; benches:\n;; insert a large 3822 rows × 16 columns table\n;; - one row at a time or as a whole\n;; - with or without undo active\n;; repeat 10 times\n;;\n;; with undo, one row at a time\n;;  (3.587732240 40 2.437140552)\n;;  (3.474445440 39 2.341087725)\n;;\n;; without undo, one row at a time\n;;  (3.127574093 33 2.001691096)\n;;  (3.238456106 33 2.089536034)\n;;\n;; with undo, single huge string\n;;  (3.030763545 30 1.842303196)\n;;  (3.012367879 30 1.841319998)\n;;\n;; without undo, single huge string\n;;  (2.499138596 21 1.419285666)\n;;  (2.403039955 21 1.338347655)\n;;       ▲       ▲      ▲\n;;       │       │      ╰──╴CPU time for GC\n;;       │       ╰─────────╴number of GC\n;;       ╰─────────────────╴overall CPU time\n\n(defun orgtbl-aggregate--elisp-table-to-string (table)\n  \"Convert TABLE to a string formatted as an Org Mode table.\nTABLE is a list of lists of cells.  The list may contain the\nspecial symbol `hline' to mean an horizontal line.\"\n  (let* ((nbcols (cl-loop\n\t\t  for row in table\n\t\t  maximize (if (listp row) (length row) 0)))\n\t (maxwidths  (make-list nbcols 1))\n\t (numbers    (make-list nbcols 0))\n\t (non-empty  (make-list nbcols 0))\n\t (spaces-cache (make-vector 100 nil)))\n\n    ;; compute maxwidths\n    (cl-loop for row in table\n\t     do\n\t     (cl-loop for cell on row\n\t\t      for mx on maxwidths\n\t\t      for nu on numbers\n\t\t      for ne on non-empty\n\t\t      for cellnp = (car cell)\n\t\t      do (cond ((not cellnp)\n\t\t\t\t(setcar cell (setq cellnp \"\")))\n\t\t       \t       ((not (stringp cellnp))\n\t\t      \t\t(setcar cell (setq cellnp (format \"%s\" cellnp)))))\n\t\t      if (string-match-p org-table-number-regexp cellnp)\n\t\t      do (setcar nu (1+ (car nu)))\n\t\t      unless (string= cellnp \"\")\n\t\t      do (setcar ne (1+ (car ne)))\n\t\t      if (< (car mx) (string-width cellnp))\n\t\t      do (setcar mx (string-width cellnp))))\n\n    ;; change meaning of numbers from quantity of cells with numbers\n    ;; to flags saying whether alignment should be left (number alignment)\n    (cl-loop for nu on numbers\n\t     for ne in non-empty\n\t     do\n\t     (setcar nu (< (car nu) (* org-table-number-fraction ne))))\n\n    ;; create well padded and aligned cells\n    (let ((bits (orgtbl-aggregate--list-create)))\n      (cl-loop for row in table\n\t       do\n\t       (if (listp row)\n\t\t   (cl-loop for cell in row\n\t\t\t    for mx in maxwidths\n\t\t\t    for nu in numbers\n\t\t\t    for pad = (- mx (string-width cell))\n                            do\n\t\t\t    (orgtbl-aggregate--list-append bits \"| \")\n\t\t\t    (cond\n\t\t\t     ;; no alignment\n                             ((<= pad 0)\n\t\t\t      (orgtbl-aggregate--list-append bits cell))\n\t\t\t     ;; left alignment\n\t\t\t     (nu\n\t\t\t      (orgtbl-aggregate--list-append bits cell)\n                              (orgtbl-aggregate--list-append\n                               bits\n                               (orgtbl-aggregate--insert-make-spaces pad spaces-cache)))\n\t\t\t     ;; right alignment\n                             (t\n\t\t\t      (orgtbl-aggregate--list-append\n                               bits\n                               (orgtbl-aggregate--insert-make-spaces pad spaces-cache))\n\t\t\t      (orgtbl-aggregate--list-append bits cell)))\n\t\t\t    (orgtbl-aggregate--list-append bits \" \"))\n\t\t (cl-loop for bar = \"|\" then \"+\"\n\t\t\t  for mx in maxwidths\n                          do\n\t\t\t  (orgtbl-aggregate--list-append bits bar)\n\t\t\t  (orgtbl-aggregate--list-append bits (make-string (+ mx 2) ?-))))\n\t       (orgtbl-aggregate--list-append bits \"|\\n\"))\n      ;; remove the last \\n because Org Mode re-adds it\n      (setcar (car bits) \"|\")\n      (mapconcat #'identity (orgtbl-aggregate--list-get bits)))))\n\n(defun orgtbl-aggregate--insert-elisp-table (table)\n  \"Insert TABLE in current buffer at point.\nTABLE is a list of lists of cells.  The list may contain the\nspecial symbol `hline' to mean an horizontal line.\"\n  ;; inactivating jit-lock-after-change boosts performance a lot\n  (cl-letf (((symbol-function 'jit-lock-after-change) (lambda (_a _b _c)) ))\n    (insert (orgtbl-aggregate--elisp-table-to-string table))))\n\n(defun orgtbl-aggregate--cell-to-string (cell)\n  \"Convert CELL (a cell in the input table) to a string if it is not already.\"\n  (cond\n   ((not cell) cell)\n   ((stringp cell) cell)\n   ((numberp cell) (number-to-string cell))\n   ((symbolp cell) (symbol-name cell))\n   (t (error \"cell %S is not a number neither a string\" cell))))\n\n(defun orgtbl-aggregate--get-header-table (table &optional asstring)\n  \"Return the header of TABLE as a list of column names.\nWhen ASSTRING is true, the result is a string which concatenates the\nnames of the columns.  TABLE may be a Lisp list of rows, or the\nname or id of a distant table.  The function takes care of\npossibly missing headers, and in this case returns a list\nof $1, $2, $3... column names.\nActual column names which are not fully alphanumeric are quoted.\"\n  (unless (consp table)\n    (setq table\n          (condition-case _err\n              (orgtbl-aggregate-table-from-any-ref table)\n            (error\n             '((\"$1\" \"$2\" \"$3\" \"…\") hline)))))\n  (orgtbl-aggregate--pop-leading-hline table)\n  (let ((header\n\t (if (memq 'hline table)\n\t     (cl-loop for x in (car table)\n                      do (setq x (orgtbl-aggregate--cell-to-string x))\n\t\t      collect\n\t\t      (if (string-match-p\n                           (rx bos nakedname eos)\n                           x)\n\t\t\t  x\n\t\t\t(format \"\\\"%s\\\"\" x)))\n\t   (cl-loop for _x in (car table)\n\t\t    for i from 1\n\t\t    collect (format \"$%s\" i)))))\n    (if asstring\n\t(mapconcat #'identity header \" \")\n      header)))\n\n;; The *this* variable is accessible to the user.\n;; It refers to the aggregated table before it is \"printed\"\n;; into the buffer, so that it can be post-processed.\n(defvar *this*)\n\n(defun orgtbl-aggregate--post-process (table post)\n  \"Post-process the aggregated TABLE according to the :post header.\nPOST might be:\n- a reference to a babel-block, for example:\n  :post \\\"myprocessor(inputtable=*this*)\\\"\n  and somewhere else:\n  #+name: myprocessor\n  #+begin_src language :var inputtable=\n  ...\n  #+end_src\n- a Lisp lambda with one parameter, for example:\n  :post (lambda (table) (append table \\\\'(hline (\\\"total\\\" 123))))\n- a Lisp function with one parameter, for example:\n  :post my-lisp-function\n- a Lisp expression which will be evaluated\n  the *this* variable will contain the TABLE\nIn all those cases, the result must be a Lisp value compliant\nwith an Org Mode table.\"\n  (cond\n   ((null post) table)\n   ((functionp post)\n    (apply post table ()))\n   ((stringp post)\n    (let ((*this* table))\n      (condition-case err\n\t  (org-babel-ref-resolve post)\n\t(error\n\t (message \"error: %S\" err)\n         (condition-case err2\n\t     (orgtbl-aggregate--post-process\n              table\n              (thing-at-point--read-from-whole-string post))\n           (error\n            (user-error\n             \":post %S ends in an error\n- as a Babel block: %s\n- not a valid Lisp expression: %s\"\n             post err err2)))))))\n   ((listp post)\n    (let ((*this* table))\n      (eval post)))\n   (t (user-error \":post %S header could not be understood\" post))))\n\n(defun orgtbl-aggregate--recover-TBLFM (content)\n  \"Return a line begining with #+tblfm: within CONTENT, if any.\"\n  (and\n   content\n   (let ((case-fold-search t))\n     (string-match\n      (rx bol (* blank) (group \"#+tblfm:\" (* nonl)))\n      content))\n   (match-string 1 content)))\n\n(defun orgtbl-aggregate--recalculate-fast ()\n  \"Wrapper arround `org-table-recalculate'.\nThe standard `org-table-recalculate' function is slow because\nit must handle lots of cases. Here the table is freshely created,\ntherefore a lot of special handling and cache updates can be\nsafely bypassed. Moreover, the alignment of the resulting table\nis delegated to orgtbl-aggregate, which is fast.\nThe result is a speedup up to x6, and a memory consumption\ndivided by up to 5. It makes a difference for large tables.\"\n  (let ((old (symbol-function 'org-table-goto-column)))\n    (cl-letf (((symbol-function 'org-fold-core--fix-folded-region)\n               (lambda (_a _b _c)))\n              ((symbol-function 'jit-lock-after-change)\n               (lambda (_a _b _c)))\n              ;; Warning: this org-table-goto-column trick fixes a bug\n              ;; in org-table.el around line 3084, when computing\n              ;; column-count. The bug prevents single-cell formulas\n              ;; creating the cell in some rare cases.\n              ((symbol-function 'org-table-goto-column)\n               (lambda (n &optional on-delim _force)\n                 ;;                            △\n                 ;;╭───────────────────────────╯\n                 ;;╰╴parameter is forcibly changed to t╶─╮\n                 ;;                      ╭───────────────╯\n                 ;;                      ▽\n                 (funcall old n on-delim t))))\n      (condition-case nil\n          (org-table-recalculate t t)\n        ;;                       △ △\n        ;; for all lines╶────────╯ │\n        ;; do not re-align╶────────╯\n        (args-out-of-range nil)))))\n\n(defun orgtbl-aggregate--table-recalculate (content formula)\n  \"Update the #+TBLFM: line and recompute all formulas.\nThe computed table may have formulas which need to be recomputed.\nThis function adds a #+TBLFM: line at the end of the table.\nIt merges old formulas (if any) contained in CONTENT,\nwith new formulas (if any) given in the `formula' directive.\"\n  (let ((tblfm (orgtbl-aggregate--recover-TBLFM content))) ;; recover a #+tblfm: line\n    (if (stringp formula)\n        ;; There is a :formula directive. Add it if not already there\n        (if tblfm\n\t    (unless (string-match-p (regexp-quote formula) tblfm)\n\t      (setq tblfm (format \"%s::%s\" tblfm formula)))\n\t  (setq tblfm (format \"#+TBLFM: %s\" formula))))\n\n    (when tblfm\n      ;; There are formulas. They need to be evaluated.\n      (end-of-line)\n      (insert \"\\n\" tblfm)\n      (forward-line -1)\n      (orgtbl-aggregate--recalculate-fast)\n\n      ;; Realign table after org-table-recalculate have changed or added\n      ;; some cells. It is way faster to re-read and re-write the table\n      ;; through orgtbl-aggregate routines than letting org-mode do the job.\n      (let* ((table (orgtbl-aggregate--table-to-lisp))\n             (width\n              (cl-loop for row in table\n                       if (consp row)\n                       maximize (length row))))\n        (cl-loop\n         for row in table\n         if (and (consp row) (< (length row) width))\n         do (nconc row (make-list (- width (length row)) nil)))\n        (delete-region (org-table-begin) (org-table-end))\n        (insert (orgtbl-aggregate--elisp-table-to-string table) \"\\n\")))))\n\n;; bazilo]\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; The Org Table Aggregation package really begins here\n\n(defun orgtbl-aggregate--replace-colnames-nth (table expression)\n  \"Replace occurrences of column names in Lisp EXPRESSION.\nReplacements are forms like (nth N row),\nN being the numbering of columns.\nDoing so, EXPRESSION is ready to be computed against a TABLE row.\"\n  (cond\n   ((listp expression)\n    (cons (car expression)\n\t  (cl-loop for x in (cdr expression)\n\t\t   collect\n\t\t   (orgtbl-aggregate--replace-colnames-nth table x))))\n   ((numberp expression)\n    expression)\n   (t\n    (let ((n (orgtbl-aggregate--colname-to-int expression table)))\n      (if n\n          `(nth ,n orgtbl-aggregate--row)\n\texpression)))))\n\n(defun orgtbl-aggregate--to-frux (formula table involved)\n  \"Parse FORMULA replacing column names with Frux(NN).\nNN is the column position as it appears in TABLE.\nTake into account protection of non-alphanumeric names\nby single or double quotes.\nAlso replace sum, mean, etc. with vsum, vmean, etc.\nthe v names being understandable by Calc.\nINVOLVED is a list to which column numbers of columns\nreferenced by formula are added.\"\n  (replace-regexp-in-string\n   (rx (quotedcolname nakedname) (? (* space) \"(\"))\n   (lambda (var)\n     (save-match-data ;; save because we are called within a replace-regexp\n       (if (string-match\n            (rx (group (+ (notany \"(\"))) (* space) \"(\")\n            var)\n\t   (if (member\n\t\t(match-string 1 var)\n\t\t'(\"mean\" \"meane\" \"gmean\" \"hmean\" \"median\" \"sum\"\n\t\t  \"min\" \"max\" \"prod\" \"pvar\" \"sdev\" \"psdev\"\n\t\t  \"corr\" \"cov\" \"pcov\" \"count\" \"span\" \"var\"))\n\t       ;; aggregate functions with or without the leading \"v\"\n\t       ;; for example, sum(X) and vsum(X) are equivalent\n\t       (format \"v%s\" var)\n\t     var)\n\t ;; replace VAR if it is a column name\n\t (let ((i (orgtbl-aggregate--colname-to-int\n\t\t   var\n\t\t   table)))\n\t   (if i\n\t       (progn\n\t\t (unless\n                     (memq i (orgtbl-aggregate--list-get involved))\n\t\t   (orgtbl-aggregate--list-append involved i))\n\t\t (format \"Frux(%s)\" i))\n\t     var)))))\n   formula\n   t ;; if nil, Frux is sometimes converted to FRUX\n   ))\n\n(defun orgtbl-aggregate--frux-to-$ (frux)\n  \"Replace all occurences of Frux(NN) by $NN in FRUX\"\n  (replace-regexp-in-string\n   (rx \"Frux(\" (group (+ digit)) \")\")\n   (lambda (var)\n     (format \"$%s\" (match-string 1 var))\n     )\n   frux))\n\n;; dynamic binding\n(defvar orgtbl-aggregate--var-keycols)\n\n(cl-defstruct\n    (orgtbl-aggregate--outcol\n     ;; (:predicate nil) ; worse with this directive\n     (:copier nil))\n  ;; (formula\tnil :readonly t) ;; :readonly has no effect\n  formula\t; user-entered formula to compute output cells\n  format\t; user-entered formatter of output cell\n  sort\t\t; user-entered sorting instruction for output column\n  invisible\t; user-entered output column invisibility\n  name\t\t; user-entered output column name\n  formula$\t; derived formula with $N instead of input column names\n  formula-frux\t; derived formula in Calc format with Frux(N) for input columns\n  involved\t; list of input columns numbers appearing in formula\n  key\t\t; is this output column a key-column?\n  )\n\n(defun orgtbl-aggregate--parse-col (col table)\n  \"Parse COL specification into an ORGTBL-AGGREGATE--OUTCOL structure.\nCOL is a column specification.  It is a string text:\n\\\"formula;formatter;^sorting;<invisible>;'alternate_name'\\\"\nIf there is no formatter or sorting or other specifier,\nnil is given in place. The other fields of orgtbl-aggregate--OUTCOL are\nfilled here too, and nowhere else.\nTABLE is used to convert a column name\ninto the column number.\"\n  ;; parse user specification\n  (unless (stringp col)\n    (setq col (format \"%s\" col)))\n  (unless (string-match\n           (rx\n            bos\n            (group-n 1 (+ (quotedcolname (notany \" ;'\\\"\"))))\n            (*\n             \";\"\n             (or\n              (seq     (group-n 2 (* (notany \"^;'\\\"<\"))))\n              (seq \"^\" (group-n 3 (* (notany \"^;'\\\"<\"))))\n              (seq \"<\" (group-n 4 (* (notany \"^;'\\\">\"))) \">\")\n              (seq \"'\" (group-n 5 (* (notany \"'\"))) \"'\")))\n            eos)\n\t   col)\n    (user-error \"Bad column specification: %S\" col))\n  (let* ((formula   (match-string 1 col))\n\t (format    (match-string 2 col))\n\t (sort      (match-string 3 col))\n\t (invisible (match-string 4 col))\n\t (name      (match-string 5 col))\n\n\t ;; list the input column numbers which are involved\n\t ;; into formula\n\t (involved (orgtbl-aggregate--list-create))\n\n\t ;; create a derived formula in Calc format,\n\t ;; where names of input columns are replaced with\n\t ;; frux(N)\n\t (frux (orgtbl-aggregate--to-frux formula table involved))\n\n\t ;; create a derived formula where input column names\n\t ;; are replaced with $N\n\t (formula$ (orgtbl-aggregate--frux-to-$ frux))\n\n\t ;; if a formula is just an input column name,\n\t ;; then it is a key-grouping-column\n\t (key\n\t  (if (string-match-p\n               (rx bos (quotedcolname nakedname) eos)\n\t       formula)\n\t      (orgtbl-aggregate--colname-to-int formula table t))))\n\n    (if key (push key orgtbl-aggregate--var-keycols))\n\n    (make-orgtbl-aggregate--outcol\n     :formula      formula\n     :format       format\n     :sort         sort\n     :invisible    invisible\n     :name         name\n     :formula$     formula$\n     :formula-frux (math-read-expr frux)\n     :involved     (orgtbl-aggregate--list-get involved)\n     :key          key)))\n\n;; dynamic binding\n(defvar orgtbl-aggregate--columns-sorting)\n\n(cl-defstruct\n    (orgtbl-aggregate--sorting\n     ;; (:predicate nil) ; worse with this directive\n     (:copier nil))\n  ;; (strength  nil :readonly t) ;; :readonly has no effect\n  strength        ; the 3 in a user specification like ;^a3\n  colnum          ; the number of the output column to sort\n  ascending       ; ;^n is ascending, ;^N is descending\n  extract         ; extract Lisp function, eg string-to-number for ;^n\n  compare         ; comparison Lisp function for 2 cells, eg string< for ;^a\n  )\n\n(defun orgtbl-aggregate--prepare-sorting (aggcols)\n  \"Create a list of columns to be sorted.\nColumns are searched into AGGCOLS.\nThe resulting list will be used by\n`orgtbl-aggregate--columns-sorting'.\nThe list contains sorting specifications as follows:\n  . sorting strength\n  . column number\n  . ascending descending\n  . extract function\n  . compare function\n- sorting strength is a number telling what column should be\n  considered first:\n  . lower number are considered first\n  . nil are condirered last\n- column number is as in the user specification\n  1 is the first user specified column\n- ascending descending is nil for ascending, t for descending\n- extract function converts the input cell (which is a string)\n  into a comparable value\n- compare function compares two cells and answers nil if\n  the first cell must come before the second.\"\n  (cl-loop for col in aggcols\n\t   for sorting = (orgtbl-aggregate--outcol-sort col)\n\t   for colnum from 0\n\t   if sorting\n\t   do\n\t   (unless (string-match\n                    (rx bol\n                        (group (any \"aAnNtTfF\"))\n                        (group (* digit))\n                        eol)\n                    sorting)\n\t     (user-error\n              \"Bad sorting specification: ^%s, expecting a/A/n/N/t/T and an optional number\"\n              sorting))\n\t   (orgtbl-aggregate--list-append\n\t    orgtbl-aggregate--columns-sorting\n\t    (let ((strength\n\t\t   (if (string= (match-string 2 sorting) \"\")\n\t\t       nil\n\t\t     (string-to-number (match-string 2 sorting)))))\n\t      (pcase (match-string 1 sorting)\n\t\t(\"a\" (record 'orgtbl-aggregate--sorting strength colnum nil #'orgtbl-aggregate--cell-to-string #'string-lessp))\n\t\t(\"A\" (record 'orgtbl-aggregate--sorting strength colnum t   #'orgtbl-aggregate--cell-to-string #'string-lessp))\n\t\t(\"n\" (record 'orgtbl-aggregate--sorting strength colnum nil #'orgtbl-aggregate--cell-to-number #'<))\n\t\t(\"N\" (record 'orgtbl-aggregate--sorting strength colnum t   #'orgtbl-aggregate--cell-to-number #'<))\n\t\t(\"t\" (record 'orgtbl-aggregate--sorting strength colnum nil #'orgtbl-aggregate--cell-to-time   #'<))\n\t\t(\"T\" (record 'orgtbl-aggregate--sorting strength colnum t   #'orgtbl-aggregate--cell-to-time   #'<))\n\t\t((or \"f\" \"F\") (user-error \"f/F sorting specification not (yet) implemented\"))\n\t\t(_ (user-error \"Bad sorting specification ^%s\" sorting))))))\n\n  ;; major sorting columns must come before minor sorting columns\n  (setq orgtbl-aggregate--columns-sorting\n\t(sort (orgtbl-aggregate--list-get orgtbl-aggregate--columns-sorting)\n\t      (lambda (a b)\n\t\t(if      (null   (orgtbl-aggregate--sorting-strength a))\n\t\t    (and (null   (orgtbl-aggregate--sorting-strength b))\n\t\t\t (<      (orgtbl-aggregate--sorting-colnum   a)\n                                 (orgtbl-aggregate--sorting-colnum   b)))\n\t\t  (or    (null   (orgtbl-aggregate--sorting-strength b))\n\t\t         (<      (orgtbl-aggregate--sorting-strength a)\n                                 (orgtbl-aggregate--sorting-strength b))\n\t\t\t (and (= (orgtbl-aggregate--sorting-strength a)\n                                 (orgtbl-aggregate--sorting-strength b))\n\t\t\t      (< (orgtbl-aggregate--sorting-colnum   a)\n                                 (orgtbl-aggregate--sorting-colnum   b)))))))))\n\n;; escape lexical binding to eval user given\n;; Lisp expression\n(defvar orgtbl-aggregate--row)\n\n(defun orgtbl-aggregate--table-add-group (groups hgroups row aggcond)\n  \"Add the source ROW to the GROUPS of rows.\nIf ROW fits a group within GROUPS, then it is added at the end\nof this group.\nOtherwise a new group is added at the end of GROUPS,\ncontaining this single ROW.\nAGGCOND is a formula which is evaluated against ROW.\nIf nil, ROW is just discarded.\nHGROUPS contains the same information as GROUPS, stored in\na hash-table, whereas GROUPS is a Lisp list.\"\n  (and (or (not aggcond)\n\t   (let ((orgtbl-aggregate--row row))\n             ;; this eval need the variable 'orgtbl-aggregate--row\n             ;; to have a value\n             (eval aggcond)))\n       (let ((gr (gethash row hgroups)))\n\t (unless gr\n\t   (setq gr (orgtbl-aggregate--list-create))\n\t   (puthash row gr hgroups)\n\t   (orgtbl-aggregate--list-append groups gr))\n\t (orgtbl-aggregate--list-append gr row))))\n\n(defun orgtbl-aggregate--read-calc-expr (expr)\n  \"Interpret EXPR (a string) as either an org date or a calc expression.\"\n  (cond\n   ;; nil happens when a table is malformed\n   ;; some columns are missing in some rows\n   ((not expr) nil)\n   ;; already an integer? return it\n   ((integerp expr) expr)\n   ;; a floating point? must convert it to Calc\n   ((numberp expr) (math-read-number (number-to-string expr)))\n   ;; empty cell returned as nil,\n   ;; to be processed later depending on modifier flags\n   ((string= expr \"\") nil)\n   ;; the purely numerical cell case arises very often\n   ;; short-circuiting general functions boosts performance (a lot)\n   ((and\n     (string-match-p\n      (rx bos\n\t  (? (any \"+-\")) (* digit)\n\t  (? \".\" (* digit))\n\t  (? \"e\" (? (any \"+-\")) (+ digit))\n\t  eos)\n      expr)\n     (not (string-match-p (rx bos (* (any \"+-.\")) \"e\") expr)))\n    (math-read-number expr))\n   ;; Convert an Org-mode date to Calc internal representation\n   ((string-match-p org-ts-regexp0 expr)\n    (math-parse-date\n     (replace-regexp-in-string (rx (any \"[<>].a-zA-Z\")) \" \" expr)))\n   ;; Convert a duration into a number of seconds\n   ((string-match\n     (rx bos\n\t (group (+ digit))\n\t \":\"\n\t (group digit digit)\n\t (? \":\" (group digit digit))\n\t eos)\n     expr)\n    (+\n     (* 3600 (string-to-number (match-string 1 expr)))\n     (*   60 (string-to-number (match-string 2 expr)))\n     (if (match-string 3 expr) (string-to-number (match-string 3 expr)) 0)))\n   ;; generic case: symbolic calc expression\n   (t\n    (math-simplify\n     (calcFunc-expand\n      (math-read-expr expr))))))\n\n(defun orgtbl-aggregate--hash-test-equal (row1 row2)\n  \"Are ROW1 & ROW2 equal regarding the key columns?\"\n  (cl-loop for idx in orgtbl-aggregate--var-keycols\n\t   always (equal (nth idx row1) (nth idx row2))))\n\n;; Use standard sxhash-equal to hash strings\n;; Unfortunately sxhash-equal is weak.\n;; So we compensate with multiplications and reminders,\n;; while trying to stay within the 2^29 fixnums.\n;; see (info \"(elisp) Integer Basics\")\n\n(defun orgtbl-aggregate--hash-test-hash (row)\n  \"Compute a hash code for ROW from key columns.\"\n  (let ((h 456542153))\n    (cl-loop for idx in orgtbl-aggregate--var-keycols\n\t     do\n             (setq\n              h\n              (*\n               (%\n                (*\n                 (%\n                  (logxor\n                   (sxhash-equal (nth idx row))\n                   h)\n                  53639)\n                 9973)\n                53633)\n               10007)))\n    h))\n\n(defun orgtbl-aggregate--parse-preprocess (formulas width)\n  \"Parse the :precompute parameter's value, FORMULAS.\nWIDTH is the number of columns of the input table, without\nthe precomputed columns.\nReturn a list:\n((formula1 . name1) (formula2 . name2) …)\"\n    ;; formulas is a list of strings\n    ;; change it to ((formula1 . name1) (formula2 . name2) …)\n    ;; name1 etc. default to a dollar name like \"$8\"\n    (if (stringp formulas)\n        (setq formulas\n              (split-string\n               formulas\n               (rx (* space) \"::\" (* space))\n               t)))\n    (cl-loop\n      for formula in formulas\n      for i from (1+ width)\n      for dollari = (format \"$%s\" i)\n      collect\n      (if (string-match\n           (rx bos\n               (group-n 1 (+ (notany \";\"))) ; formula to compute column\n               (*\n                \";\" (* space) ; maybe something after a semicolon\n                (or\n                 (seq     (group-n 2 (+ (notany \"^;'\\\"<\")))) ; a formatter\n                 (seq \"'\" (group-n 3 (* (notany \"'\"))) \"'\") ; column name\n                 (seq \"\")))             ; nothing after semicolon\n               (* space)\n               eos)\n           formula)\n          (cons\n           (if (match-string 2 formula) ; case formula;formatter\n               (format \"%s;%s\" (match-string 1 formula) (match-string 2 formula))\n             (match-string 1 formula))  ; case formula without formatter\n           (or (match-string 3 formula) dollari))    ; new column's name\n        (cons formula dollari))))\n\n(defun orgtbl-aggregate--enrich-table (table formulas)\n  \"Enrich TABLE with new columns computed by FORMULAS.\nThe FORMULAS are supposed to be those used in spreadsheets,\nas given after the #+TBLFM: tag.\nActually, FORMULAS are evaluated by Org, not by orgtbl-aggregate.\"\n  (let ((start (point))\n        (width (length (car table))))\n    (setq\n     formulas\n     (orgtbl-aggregate--parse-preprocess formulas width))\n    (if (memq 'hline table)\n        ;; table has a header? add it the names of the new columns\n        (cl-loop\n         for formula in formulas\n         do (nconc (car table) (list (cdr formula))))\n      ;; table does not have a header? add one of the form:\n      ;; (\"$1\" \"$2\" … \"$7\" \"name1\" \"name2\" …)\n      (setq\n       table\n       (cons\n        (append\n         (cl-loop for i from 1 to width collect (format \"$%s\" i))\n         (cl-loop for formula in formulas collect (cdr formula)))\n        (cons 'hline table))))\n\n    (insert \"\\n#+TBLFM: \")\n    (let ((involved (orgtbl-aggregate--list-create)))\n      (cl-loop\n       for formula in formulas\n       for i from (1+ width)\n       do\n       (insert\n        (format\n         \"::$%s=%s\"\n         i\n         (orgtbl-aggregate--frux-to-$\n          (orgtbl-aggregate--to-frux (car formula) table involved))))))\n    (forward-line -1)\n    (orgtbl-aggregate--insert-elisp-table table)\n\n    ;; ask Org to evaluate the formulas and fill the new columns\n    (orgtbl-aggregate--recalculate-fast)\n    (prog1\n        ;; recover the enriched table a Lisp structure\n        (orgtbl-aggregate--table-to-lisp)\n      ;; leave the buffer space between #+begin: and #+end:\n      ;; as empty as it was prior to entering this function\n      (delete-region\n       start\n       (let ((case-fold-search t))\n         (search-forward-regexp (rx bol \"#+end:\" eol))\n         (beginning-of-line)\n         (point)))\n      (insert \"\\n\")\n      (goto-char start))))\n\n(defun orgtbl-aggregate--add-hlines (result hline)\n  \"Add hlines to RESULT between different blocks of rows.\nHLINE is a small number (1 or 2 or 3, maybe more)\nwhich gives the number of sorted columns to consider\nto split rows blocks with hlines.\nhlines are added in-place\"\n  (let ((colnums\n\t (cl-loop for col in orgtbl-aggregate--columns-sorting\n\t\t  for n from 1 to hline\n\t\t  collect (orgtbl-aggregate--sorting-colnum col))))\n    (cl-loop for row on result\n\t     unless\n\t     (or (null oldrow)\n\t\t (cl-loop for c in colnums\n\t\t\t  always\n                          (equal\n\t\t\t   (nth c (orgtbl-aggregate--list-get (car row)))\n\t\t\t   (nth c (orgtbl-aggregate--list-get (car oldrow))))))\n\t     do (setcdr oldrow (cons 'hline (cdr oldrow)))\n\t     for oldrow = row)))\n\n(defun orgtbl-aggregate--create-table-aggregated (table params)\n  \"Convert the source TABLE into an aggregated table.\nThe source TABLE is a list of lists of cells.\nThe resulting table follows the specifications,\nfound in PARAMS entry :cols, ignoring source rows\nwhich do not pass the filter found in PARAMS entry :cond.\"\n  (orgtbl-aggregate--pop-leading-hline table)\n  (define-hash-table-test\n    'orgtbl-aggregate--hash-test-name\n    #'orgtbl-aggregate--hash-test-equal\n    #'orgtbl-aggregate--hash-test-hash)\n  (let ((groups (orgtbl-aggregate--list-create))\n\t(hgroups (make-hash-table :test 'orgtbl-aggregate--hash-test-name))\n\t(aggcols    (plist-get params :cols))\n\t(aggcond    (plist-get params :cond))\n\t(hline      (plist-get params :hline))\n        (precompute (plist-get params :precompute))\n\t;; a global variable, passed to the sort predicate\n\t(orgtbl-aggregate--columns-sorting (orgtbl-aggregate--list-create))\n\t;; another global variable\n\t(orgtbl-aggregate--var-keycols))\n\n    (if precompute\n        (setq table (orgtbl-aggregate--enrich-table table precompute)))\n\n    (unless aggcols\n      (setq aggcols (orgtbl-aggregate--get-header-table table)))\n    (if (stringp aggcols)\n\t(setq aggcols (orgtbl-aggregate--split-string-with-quotes aggcols)))\n    (cl-loop for col on aggcols\n\t     do (setcar col (orgtbl-aggregate--parse-col (car col) table)))\n    (when aggcond\n      (if (stringp aggcond)\n\t  (setq aggcond (read aggcond)))\n      (setq aggcond\n\t    (orgtbl-aggregate--replace-colnames-nth table aggcond)))\n    (setq hline\n\t  (cond ((null hline)\n\t\t 0)\n\t\t((numberp hline)\n\t\t hline)\n\t\t((string-match-p (rx bos (or \"yes\" \"t\") eos) hline)\n\t\t 1)\n\t\t((string-match-p (rx bos (or \"no\" \"nil\") eos) hline)\n\t\t 0)\n\t\t((string-match-p \"[0-9]+\" hline)\n\t\t (string-to-number hline))\n\t\t(t\n\t\t (user-error\n                  \":hline parameter should be 0, 1, 2, 3, ... or yes, t, no, nil, not %S\"\n                  hline))))\n\n    ;; special case: no sorting column but :hline 1 required\n    ;; then a hidden hline column is added\n    (if (and (> hline 0)\n\t     (cl-loop for col in aggcols\n\t\t      never (orgtbl-aggregate--outcol-sort col)))\n\t(push\n\t (orgtbl-aggregate--parse-col \"hline;^n;<>\" table)\n\t aggcols))\n\n    (orgtbl-aggregate--prepare-sorting aggcols)\n\n    ; split table into groups of rows\n    (cl-loop\n     with nbcols\n     = (cl-loop\n        for row in table\n        maximize (if (listp row) (length row) 0))\n     with hline = 0\n     for rownum from 1\n     for row in\n     (or (cdr (memq 'hline table)) ;; skip header if any\n\t table)\n     do\n     (cond\n      ((eq row 'hline)\n       (setq hline (1+ hline)))\n      ((listp row)\n       ;; fix too short rows\n       (if (< (length row) nbcols)\n           (setq row (nconc row (make-list (- nbcols (length row)) \"\"))))\n       (orgtbl-aggregate--table-add-group\n\tgroups\n\thgroups\n        ;; add rownum at beginning and hline at end\n        (cons rownum (nconc row (list hline)))\n\taggcond))))\n\n    (let ((result ;; pre-allocate all resulting rows\n\t   (cl-loop for _x in (orgtbl-aggregate--list-get groups)\n\t\t    collect (orgtbl-aggregate--list-create)))\n\t  (all-$list\n\t   (cl-loop for _x in (orgtbl-aggregate--list-get groups)\n\t\t    collect\n                    (make-vector\n                     ;; + 2 for rownum at 0 & hline at the end\n                     (+ 2 (length (car table)))\n                     nil))))\n\n      ;; inactivating those two functions boosts performance\n      (cl-letf (((symbol-function 'math-read-preprocess-string) #'identity)\n\t\t((symbol-function 'calc-input-angle-units) (lambda (_x) nil)))\n\t;; do aggregation\n\t(cl-loop for coldesc in aggcols\n\t\t do\n\t\t (orgtbl-aggregate--compute-sums-on-one-column\n                  groups result coldesc all-$list)))\n\n      ;; sort table according to columns described in\n      ;; orgtbl-aggregate--columns-sorting\n      (if orgtbl-aggregate--columns-sorting ;; are there sorting instructions?\n\t  (setq result (sort result #'orgtbl-aggregate--sort-predicate)))\n\n      ;; add hlines if requested\n      (if (> hline 0)\n\t  (orgtbl-aggregate--add-hlines result hline))\n\n      (push 'hline result)\n\n      ;; add other lines of the original header, if any;\n      ;; this is done only if the aggregated column refers to\n      ;; a single source column (either a key column or within\n      ;; an aggregated formula)\n      (orgtbl-aggregate--pop-leading-hline table)\n\n      (if (memq 'hline table)\n          (cl-loop\n           for i from (cl-loop\n                       for i from -1\n                       for x in table\n                       until (eq x 'hline)\n                       finally return i)\n           downto 1\n           do (push\n               (cons\n                nil\n                (cl-loop for column in aggcols\n                         collect\n                         (if (eq (length (orgtbl-aggregate--outcol-involved column)) 1)\n                             (let ((n (1- (car (orgtbl-aggregate--outcol-involved column)))))\n                               (if (>= n 0)\n                                   (nth n (nth i table))\n                                 \"\"))\n                           \"\")))\n               result)))\n\n      ;; add the header to the resulting table with column names\n      ;; as they appear in :cols but without decorations\n      (push\n       (cons\n        nil\n\t(cl-loop for column in aggcols\n\t\t collect (or\n\t\t\t  (orgtbl-aggregate--outcol-name column)\n                          (replace-regexp-in-string\n                           \"['\\\"]\" \"\"\n\t\t\t   (orgtbl-aggregate--outcol-formula column)))))\n       result)\n\n      ;; remove invisible columns by modifying the table in-place\n      ;; beware! it assumes that the actual list in orgtbl-aggregate--lists\n      ;; is pointed to by the cdr of the orgtbl-aggregate--list\n      (if (cl-loop for col in aggcols\n\t\t   thereis (orgtbl-aggregate--outcol-invisible col))\n\t  (cl-loop for row in result\n\t\t   if (consp row)\n\t\t   do (cl-loop for col in aggcols\n\t\t\t       with cel = row\n\t\t\t       if (orgtbl-aggregate--outcol-invisible col)\n\t\t\t       do    (setcdr cel (cddr cel))\n\t\t\t       else do (orgtbl-aggregate--pop-simple cel))))\n\n      ;; change appendable-lists to regular lists\n      (cl-loop for row on result\n\t       if (consp (car row))\n\t       do (setcar row (orgtbl-aggregate--list-get (car row))))\n\n      result)))\n\n(defun orgtbl-aggregate--sort-predicate (rowa rowb)\n  \"Compares ROWA & ROWB (which are Org Mode table rows)\naccording to orgtbl-aggregate--columns-sorting instructions.\nReturn nil if ROWA already comes before ROWB.\"\n  (setq rowa (orgtbl-aggregate--list-get rowa))\n  (setq rowb (orgtbl-aggregate--list-get rowb))\n  (cl-loop for col in orgtbl-aggregate--columns-sorting\n\t   for colnum  = (orgtbl-aggregate--sorting-colnum    col)\n\t   for desc    = (orgtbl-aggregate--sorting-ascending col)\n\t   for extract = (orgtbl-aggregate--sorting-extract   col)\n\t   for compare = (orgtbl-aggregate--sorting-compare   col)\n\t   for cella   = (funcall extract (nth colnum (if desc rowb rowa)))\n\t   for cellb   = (funcall extract (nth colnum (if desc rowa rowb)))\n\t   thereis (funcall compare cella cellb)\n\t   until   (funcall compare cellb cella)))\n\n(defun orgtbl-aggregate--cell-to-time (cell)\n  \"Interprete the string CELL into a duration in minutes.\nThe code was borrowed from org-table.el.\"\n  (cond\n   ((numberp cell) cell)\n   ((not (stringp cell))\n    (error \"cell %S is neither a string nor a number to be converted to time\"\n           cell))\n   ((string-match org-ts-regexp-both cell)\n    (float-time\n     (org-time-string-to-time (match-string 0 cell))))\n   ((org-duration-p cell) (org-duration-to-minutes cell))\n   ((string-match\n     (rx bow (+ digit) \":\" (= 2 digit) eow)\n     cell)\n    (org-duration-to-minutes (match-string 0 cell)))\n   (t 0)))\n\n(defun orgtbl-aggregate--cell-to-number (cell)\n  \"Convert CELL (a cell in the input table) to a number if it is not already.\"\n  (cond\n   ((numberp cell) cell)\n   ((stringp cell) (string-to-number cell))\n   (t (error \"cell %S is not a number neither a string\" cell))))\n\n(defun orgtbl-aggregate--fmt-settings (fmt)\n  \"Convert the FMT user-given format.\nResult is the FMT-SETTINGS assoc list.\"\n  (let ((fmt-settings (plist-put () :fmt nil)))\n    (when fmt\n      ;; the following code was freely borrowed from org-table-eval-formula\n      ;; not all settings extracted from fmt are used\n      (while (string-match\n              (rx (group (any \"pnfse\")) (group (? \"-\") (+ digit)))\n              fmt)\n\t(let ((c (string-to-char   (match-string 1 fmt)))\n\t      (n (string-to-number (match-string 2 fmt))))\n          (cl-case c\n            (?p (setq calc-internal-prec n))\n\t    (?n (setq calc-float-format `(float ,n)))\n\t    (?f (setq calc-float-format `(fix   ,n)))\n\t    (?s (setq calc-float-format `(sci   ,n)))\n\t    (?e (setq calc-float-format `(eng   ,n)))))\n\t(setq fmt (replace-match \"\" t t fmt)))\n      (while (string-match \"[tTUNLEDRFSuQqCc]\" fmt)\n        (cl-case (string-to-char (match-string 0 fmt))\n          (?t (plist-put fmt-settings :duration t)\n\t      (plist-put fmt-settings :numbers  t)\n\t      (plist-put fmt-settings :duration-output-format org-table-duration-custom-format))\n          (?T (plist-put fmt-settings :duration t)\n\t      (plist-put fmt-settings :numbers  t)\n\t      (plist-put fmt-settings :duration-output-format nil))\n          (?U (plist-put fmt-settings :duration t)\n\t      (plist-put fmt-settings :numbers  t)\n\t      (plist-put fmt-settings :duration-output-format 'hh:mm))\n          (?N (plist-put fmt-settings :numbers  t))\n          (?L (plist-put fmt-settings :literal t))\n          (?E (plist-put fmt-settings :keep-empty t))\n\t  (?D (setq calc-angle-mode 'deg))\n\t  (?R (setq calc-angle-mode 'rad))\n\t  (?F (setq calc-prefer-frac t))\n\t  (?S (setq calc-symbolic-mode t))\n          (?u (setq calc-simplify-mode 'units))\n          (?c (plist-put fmt-settings :debug ?c))\n          (?C (plist-put fmt-settings :debug ?C))\n          (?q (plist-put fmt-settings :debug ?q))\n\t  (?Q (plist-put fmt-settings :debug ?Q)))\n\t(setq fmt (replace-match \"\" t t fmt)))\n      (when (string-match-p (rx (not (syntax whitespace))) fmt)\n\t(plist-put fmt-settings :fmt fmt)))\n    fmt-settings))\n\n(eval-when-compile\n  (defmacro orgtbl-aggregate--calc-setting (setting &optional setting0)\n    \"Retrieve a Calc setting.\nThe setting comes either from `org-calc-default-modes'\nor from SETTING itself.\nSETTING0 is a default to use if both fail.\"\n    ;; plist-get would be fine, except that there is no way\n    ;; to distinguish a value of nil from no value\n    ;; so we fallback to memq\n    `(let ((x (memq (quote ,setting) org-calc-default-modes)))\n       (if x (cadr x)\n         (or ,setting ,setting0))))\n  )\n\n(defun orgtbl-aggregate--compute-sums-on-one-column (groups result coldesc all-$list)\n  \"Apply COLDESC over all GROUPS of rows.\nCOLDESC is a formula given by the user in :cols,\nwith an optional format.\nCommon Calc settings and formats are pre-computed before\nactually computing sums, because they are the same for all groups.\nRESULT is the list of expected resulting rows.\nAt the beginning, all rows are empty lists.\nA cell is appended to every row at each call of this function.\"\n\n  ;; within this (let), we locally set Calc settings that must be active\n  ;; for all the calls to Calc:\n  ;; (orgtbl-aggregate--read-calc-expr) and (math-format-value)\n  (let ((calc-internal-prec\n \t (orgtbl-aggregate--calc-setting calc-internal-prec))\n\t(calc-float-format\n  \t (orgtbl-aggregate--calc-setting calc-float-format ))\n\t(calc-angle-mode\n    \t (orgtbl-aggregate--calc-setting calc-angle-mode   ))\n\t(calc-prefer-frac\n   \t (orgtbl-aggregate--calc-setting calc-prefer-frac  ))\n\t(calc-symbolic-mode\n \t (orgtbl-aggregate--calc-setting calc-symbolic-mode))\n\t(calc-date-format\n   \t (orgtbl-aggregate--calc-setting calc-date-format '(YYYY \"-\" MM \"-\" DD \" \" www (\" \" hh \":\" mm))))\n\t(calc-display-working-message\n         (orgtbl-aggregate--calc-setting calc-display-working-message))\n\t(fmt-settings nil)\n\t(case-fold-search nil))\n\n    ;; get that out of the (let) because its purpose is to override\n    ;; what the (let) has set\n    (setq fmt-settings\n          (orgtbl-aggregate--fmt-settings\n           (orgtbl-aggregate--outcol-format coldesc)))\n\n    (cl-loop for group in (orgtbl-aggregate--list-get groups)\n\t     for row in result\n\t     for $list in all-$list\n\t     do\n\t     (orgtbl-aggregate--list-append\n\t      row\n\t      (orgtbl-aggregate--compute-one-sum\n\t       group\n\t       coldesc\n\t       fmt-settings\n\t       $list)))))\n\n(defun orgtbl-aggregate--compute-one-sum (group coldesc fmt-settings $list)\n  \"Apply a user given formula to one GROUP of input rows.\nCOLDESC is a structure where several parameters are packed:\nsee (cl-defstruct orgtbl-aggregate--outcol ...).\nThose parameters all describe a single column.\nThe formula is contained in COLDESC-formula-frux.\nColumn names have been replaced by Frux(1), Frux(2), Frux(3)... forms.\nThose Frux(N) froms are placeholders that will be replaced\nby Calc vectors of values extracted from the input table,\nin column N.\nCOLDESC-involved is a list of columns numbers used by COLDESC-formula-frux.\n$LIST is a Lisp-vector of Calc-vectors of values from the input table\nparsed by Calc. $LIST acts as a cache. When a value is missing, it is\ncomputed, and stored in $LIST. But if there is already a value,\na re-computation is saved.\nFMT-SETTINGS are formatter settings computed by\n`orgtbl-aggregate--fmt-settings', from user given formatting instructions.\nReturn an output cell.\nWhen coldesc-key is non-nil, then a key-column is considered,\nand a cell from any row in the group is returned.\"\n  (cond\n   ;; key column\n   ((orgtbl-aggregate--outcol-key coldesc)\n    (nth (orgtbl-aggregate--outcol-key coldesc)\n\t (car (orgtbl-aggregate--list-get group))))\n   ;; do not evaluate, output Calc formula\n   ((eq (plist-get fmt-settings :debug) ?c)\n    (orgtbl-aggregate--outcol-formula$ coldesc))\n   ;; do not evaluate, output Lisp formula\n   ((eq (plist-get fmt-settings :debug) ?q)\n    (orgtbl-aggregate--outcol-formula-frux coldesc))\n   ;; vlist($3) alone, without parenthesis or other decoration\n   ((string-match\n     (rx bos (? ?v) \"list\"\n\t (* blank) \"(\" (* blank)\n\t \"$\" (group (+ digit))\n\t (* blank) \")\" (* blank) eos)\n     (orgtbl-aggregate--outcol-formula$ coldesc))\n    (mapconcat\n     #'identity ;; there is fast path when `identity' is requested\n     (cl-loop with i =\n\t      (string-to-number\n               (match-string 1 (orgtbl-aggregate--outcol-formula$ coldesc)))\n\t      for row in (orgtbl-aggregate--list-get group)\n\t      collect (orgtbl-aggregate--cell-to-string (nth i row)))\n     \", \"))\n   (t\n    ;; all other cases: handle them to Calc\n    (let ((calc-dollar-values-oo\n\t   (orgtbl-aggregate--make-calc-$-list\n\t    group\n\t    fmt-settings\n\t    (orgtbl-aggregate--outcol-involved coldesc)\n\t    $list))\n\t  (calc-command-flags nil)\n\t  (calc-next-why nil)\n\t  (calc-language 'flat)\n\t  (calc-dollar-used 0))\n      (let ((ev\n\t     (orgtbl-aggregate--defrux\n\t      (orgtbl-aggregate--outcol-formula-frux coldesc)\n\t      calc-dollar-values-oo\n\t      (length (orgtbl-aggregate--list-get group)))))\n        (cond\n         ((eq (plist-get fmt-settings :debug) ?C)\n          (math-format-value ev))\n         ((eq (plist-get fmt-settings :debug) ?Q)\n          (format \"%S\" ev))\n         ((progn\n            (setq ev\n                  (math-format-value\n\t           (math-simplify\n\t            (calcFunc-expand\t  ; yes, double expansion\n\t\t     (calcFunc-expand  ; otherwise it is not fully expanded\n\t\t      (math-simplify\n                       ev))))\n                   1000))\n            (plist-get fmt-settings :fmt))\n\t  (format (plist-get fmt-settings :fmt) (string-to-number ev)))\n\t ((plist-get fmt-settings :duration)\n\t  (org-table-time-seconds-to-string\n\t   (string-to-number ev)\n\t   (plist-get fmt-settings :duration-output-format)))\n\t (t ev)))))))\n\n(defun orgtbl-aggregate--defrux (formula-frux calc-dollar-values-oo count)\n  \"Replace all Frux(N) expressions in FORMULA-FRUX.\nReplace with Calc-vectors found in CALC-DOLLAR-VALUES-OO.\nAlso replace vcount() forms with the actual number of rows\nin the current group, given by COUNT.\"\n  (cond\n   ((not (consp formula-frux))\n    formula-frux)\n   ((eq (car formula-frux) 'calcFunc-Frux)\n    (nth (cadr formula-frux) calc-dollar-values-oo))\n   ((eq (car formula-frux) 'calcFunc-vcount)\n    count)\n   (t\n    (cl-loop\n     for x in formula-frux\n     collect (orgtbl-aggregate--defrux x calc-dollar-values-oo count)))))\n\n(defun orgtbl-aggregate--make-calc-$-list (group fmt-settings involved $list)\n  \"Prepare a list of vectors that will use to replace Frux(N) expressions.\nFrux(1) will be replaced by the first element of list,\nFrux(2) by the second an so on.\nThe vectors follow the Calc syntax: (vec a b c ...).\nThey contain values extracted from rows of the current GROUP.\nVectors are created only for column numbers in INVOLVED.\nIn FMT-SETTINGS, :keep-empty is a flag to tell whether an empty cell\nshould be converted to NAN or ignored.\n:numbers is a flag to replace non numeric values by 0.\"\n  (cl-loop\n   for i in involved\n   unless (aref $list i)\n   do (aset\n       $list i\n       (cons 'vec\n\t     (cl-loop for row in (orgtbl-aggregate--list-get group)\n\t\t      collect\n\t\t      (orgtbl-aggregate--read-calc-expr (nth i row))))))\n  (cl-loop\n   for vec across $list\n   for i from 0\n   collect\n   (when (memq i involved)\n     (let ((vecc\n\t    (if (plist-get fmt-settings :keep-empty)\n\t\t(cl-loop for x in vec\n\t\t\t collect (if x x '(var nan var-nan)))\n\t      (cl-loop for x in vec\n\t\t       if x\n\t\t       collect x))))\n       (if (plist-get fmt-settings :numbers)\n\t   (cl-loop for x on (cdr vecc)\n\t\t    unless (math-numberp (car x))\n\t\t    do (setcar x 0)))\n       vecc))))\n\n;; aggregation in Push mode\n\n;;;###autoload\n(defun orgtbl-to-aggregated-table (table params)\n  \"Convert the Org Mode TABLE to an aggregated version.\n\nThe resulting table contains aggregated material.\nGrouping of rows is done for identical values of grouping columns.\nFor each group, aggregation (sum, mean, etc.) is done for other columns.\n\nThe source table must contain sending directives with the following format:\n#+ORGTBL: SEND destination orgtbl-to-aggregated-table :cols ... :cond ...\n\nThe destination must be specified somewhere in the same file\nwith a block like this:\n  #+BEGIN RECEIVE ORGTBL destination\n  #+END RECEIVE ORGTBL destination\n\nPARAMS are parameters given in the #+ORGTBL: SEND line.\n\n:cols     gives the specifications of the resulting columns.\n          It is a space-separated list of column specifications.\n          Example:\n             P Q sum(X) max(X) mean(Y)\n          Which means:\n             group rows with similar values in columns P and Q,\n             and for each group, compute the sum of elements in\n             column X, etc.\n\n          The specification for a resulting column may be:\n             COL              the name of a grouping column in the source table\n             hline            a special name for grouping rows separated\n                              by horizontal lines\n             count()          give the number of rows in each group\n             list(COL)        list the values of the column for each group\n             sum(COL)         sum of the column for each group\n             sum(COL1*COL2)   sum of the product of two columns for each group\n             mean(COL)        average of the column for each group\n             mean(COL1*COL2)  average of the product of two columns per group\n             meane(COL)       average and estimated error\n             hmean(COL)       harmonic average\n             gmean(COL)       geometric average\n             median(COL)      middle element after sorting them in each group\n             max(COL)         largest element of each group\n             min(COL)         smallest element of each group\n             sdev(COL)        standard deviation (divide by N-1)\n             psdev(COL)       population standard deviation (divide by N)\n             pvar(COL)        variance of values in each group\n             prod(COL)        product of values in each group\n             cov(COL1,COL2)   covariance of two columns, per group (div. by N-1)\n             pcov(COL1,COL2)  population covariance of two columns (div. by N)\n             corr(COL1,COL2)  linear correlation of two columns\n\n:cond     optional\n          A lisp expression to filter out rows in the source table.\n          When the expression evaluate to nil for a given row of the\n          source table, then this row is discarded in the resulting table.\n          Example:\n             (equal Q \\\"b\\\")\n          Which means: keep only source rows for which the column Q\n          has the value b\n\nNames of columns in the source table may be in the dollar form,\nfor example use $3 to name the 3th column,\nor by its name if the source table have a header.\nIf all column names are in the dollar form,\nthe table is supposed not to have a header.\nThe special column name \\\"hline\\\" takes values from zero and up\nand is incremented by one for each horizontal line.\n\nExample:\nadd a line like this one before your table\n,#+ORGTBL: SEND aggregatedtable orgtbl-to-aggregated-table \\\\\n           :cols \\\"sum(X) q sum(Y) mean(Z) sum(X*X)\\\"\nthen add somewhere in the same file the following lines:\n,#+BEGIN RECEIVE ORGTBL aggregatedtable\n,#+END RECEIVE ORGTBL aggregatedtable\nType \\\\<org-mode-map> & \\\\[org-ctrl-c-ctrl-c] into your source table\n\nNote:\n This is the \\\"push\\\" mode for aggregating a table.\n To use the \\\"pull\\\" mode, look at the org-dblock-write:aggregate function.\n\nNote:\n The name `orgtbl-to-aggregated-table' follows the Org Mode standard\n with functions like `orgtbl-to-csv', `orgtbl-to-html'...\"\n  (interactive)\n  (orgtbl-aggregate--elisp-table-to-string\n   (orgtbl-aggregate--post-process\n    (orgtbl-aggregate--create-table-aggregated table params)\n    (plist-get params :post))))\n\n;; aggregation in Pull mode\n\n(defun orgtbl-aggregate--remove-cookie-lines (table)\n  \"Remove lines of TABLE which contain cookies.\nBut do not remove cookies in the header, if any.\nThe operation is destructive.  But on the other hand,\nif there are no cookies in TABLE, TABLE is returned\nwithout any change.\nA cookie is an alignment instruction like:\n  <l>   left align cells in this column\n  <c>   center cells\n  <r>   right align\n  <15>  make this column 15 characters wide.\"\n  (orgtbl-aggregate--pop-leading-hline table)\n  (cl-loop\n   with hline = nil\n   for line on table\n   if (and hline\n           (cl-loop\n            for cell in (car line)\n            thereis\n            (and (stringp cell)\n                 (string-match-p\n                  (rx bos \"<\" (? (any \"lcr\")) (* digit) \">\" eos)\n                  cell))))\n   do (setcar line t)\n   if (eq (car line) 'hline)\n   do (setq hline t))\n  (delq t table))\n\n;;;###autoload\n(defun org-dblock-write:aggregate (params)\n  \"Create a table which is the aggregation of material from another table.\nGrouping of rows is done for identical values of grouping columns.\nFor each group, aggregation (sum, mean, etc.) is done for other columns.\n\nPARAMS contains user parameters given on the #+BEGIN: aggregate line,\nas follow:\n\n:table    name of the source table\n\n:cols     gives the specifications of the resulting columns.\n          It is a space-separated list of column specifications.\n          Example:\n             \\\"P Q sum(X) max(X) mean(Y)\\\"\n          Which means:\n             group rows with similar values in columns P and Q,\n             and for each group, compute the sum of elements in\n             column X, etc.\n\n          The specification for a resulting column may be:\n             COL              the name of a grouping column in the source table\n             hline            a special name for grouping rows separated\n                              by horizontal lines\n             count()          number of rows in each group\n             list(COL)        list the values of the column for each group\n             sum(COL)         sum of the column for each group\n             sum(COL1*COL2)   sum of the product of two columns for each group\n             mean(COL)        average of the column for each group\n             mean(COL1*COL2)  average of the product of two columns per group\n             meane(COL)       average along with the estimated error per group\n             hmean(COL)       harmonic average per group\n             gmean(COL)       geometric average per group\n             median(COL)      middle element after sorting them, per group\n             max(COL)         largest element of each group\n             min(COL)         smallest element of each group\n             sdev(COL)        standard deviation (divide by N-1)\n             psdev(COL)       population standard deviation (divide by N)\n             pvar(COL)        variance per group\n             prod(COL)        product per group\n             cov(COL1,COL2)   covariance of two columns per group (div. by N-1)\n             pcov(COL1,COL2)  population covariance of two columns (div. by N)\n             corr(COL1,COL2)  linear correlation of two columns, per group\n\n:cond     optional\n          A Lisp expression to filter out rows in the source table.\n          When the expression evaluate to nil for a given row of\n          the source table, then this row is discarded in the resulting table\n          Example:\n             (equal Q \\\"b\\\")\n          Which means: keep only source rows for which the column Q\n          has the value b.\n\nNames of columns in the source table may be in the dollar form,\nfor example $3 to name the 3th column,\nor by its name if the source table have a header.\nIf all column names are in the dollar form,\nthe table is supposed not to have a header.\nThe special column name \\\"hline\\\" takes values from zero and up\nand is incremented by one for each horizontal line.\n\nExample:\n- Create an empty dynamic block like this:\n  #+BEGIN: aggregate :table originaltable \\\\\n           :cols \\\"sum(X) Q sum(Y) mean(Z) sum(X*X)\\\"\n  #+END\n- Type \\\\<org-mode-map> & \\\\[org-ctrl-c-ctrl-c] over the BEGIN line\n  this fills in the block with an aggregated table\n\nNote:\n This is the \\\"pull\\\" mode for aggregating a table.\n To use the \\\"push\\\" mode,\n look at the `orgtbl-to-aggregated-table' function.\n\nNote:\n The name `org-dblock-write:aggregate' is constrained\n by the `org-update-dblock' function.\"\n  (interactive)\n  (let ((formula (plist-get params :formula))\n\t(content (plist-get params :content))\n\t(post    (plist-get params :post)))\n    (if content\n\t(let ((case-fold-search t))\n\t  (string-match\n\t   (rx bos\n               (* (* blank) \"\\n\")\n               (group (* (* blank) (? \"#+\" (* nonl)) \"\\n\")))\n\t   content)\n          (insert\n           (replace-regexp-in-string\n            (rx bol (* (or blank \"\\n\")) eos)\n            \"\"\n            (replace-regexp-in-string\n             (rx bol \"#+tblfm\" (* (or any \"\\n\")) eos)\n             \"\"\n             (match-string 1 content))))))\n    (orgtbl-aggregate--insert-elisp-table\n     (orgtbl-aggregate--post-process\n      (orgtbl-aggregate--create-table-aggregated\n       (orgtbl-aggregate--remove-cookie-lines\n        (orgtbl-aggregate-table-from-any-ref (plist-get params :table)))\n       params)\n      post))\n    (orgtbl-aggregate--table-recalculate content formula)))\n\n;; [bazilo synchronize orgtbl-αggregate & orgtbl-joιn\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Wizard\n\n;; This variable contains the history of user entered answers,\n;; so that they can be entered again or edited.\n(defvar orgtbl-aggregate-history-cols ())\n\n(defun orgtbl-aggregate--parse-header-arguments (type)\n  \"If (point) is on a #+begin: line, parse it, and return a plist.\nTYPE is \\\"aggregate\\\", or possibly any type of block.\nIf the line the (point) is on do not match TYPE, return nil.\"\n  (let ((line (buffer-substring-no-properties\n               (line-beginning-position)\n               (line-end-position)))\n        (case-fold-search t))\n    (and\n     (string-match\n      (rx bos (* blank) \"#+begin:\" (* blank) (group (+ word)) (group (* nonl)) eos)\n      line)\n     (equal (match-string 1 line) type)\n     (let ((list (read (concat \"(\" (match-string 2 line) \")\"))))\n       (cl-loop\n        for pair on list\n        for val = (cadr pair)\n        do\n        (if (symbolp val)\n            (setcar (cdr pair) (symbol-name val)))\n        (setq pair (cdr pair)))\n       list))))\n\n(defun orgtbl-aggregate--dismiss-help ()\n  \"Hide the wizard help window.\"\n  (let ((help-window (get-buffer-window \"*orgtbl-aggregate-help*\")))\n    (if help-window\n        (delete-window help-window))))\n\n(defun orgtbl-aggregate--display-help (explain &rest args)\n  \"Display help for each field the wizard queries.\nEXPLAIN is a text in Org Mode to display. It is process\nthrough `format' with replacements in ARGS.\"\n  (let ((docs\n         '(\n           :isorgid \"* Input table locator\nThe input table may be pointed to by:\n- a file and a name,\n- an Org Mode identifier\"\n           :orgid \"* Org ID\nIt is an identifier hidden in a =properties= drawer.\nOrg Mode globally keeps track of all Ids and knows how to access them.\nIt is supposed that the ID location is followed by a table or\na Babel block suitable for aggregation.\"\n           :name \"* The input table may be:\n- a regular Org table,\n- a Babel block whose output will be the input table.\nOrg table & Babel block names are available at completion (type ~TAB~).\nLeave empty for a CSV or JSON formatted table.\"\n           :params \"* Parameters for Babel code block (optional)\n** A Babel code block may require specific parameters\nGive them here if needed, surrounded by parenthesis. Example:\n  ~(size=4,reverse=nil)~\n** CSV or JSON formatted tables.\nExamples:\n  ~(csv header)~ ~(json)~\n** Regular Org Mode table\nLeave empty.\"\n           :slice \"* Slicing (optional)\nSlicing is an Org Mode feature allowing to cut the input table.\nIt applies to any input: Org table, Babel output, CSV, JSON.\nLeave empty for no slicing.\n** Examples:\n- ~mytable[0:5]~     retains only the first 6 rows of the input table\n- ~mytable[*,0:1]~   retains only the first 2 columns\n- ~mytable[0:5,0:1]~ retains 5 rows and 2 columns\"\n           :cond \"* Filter rows (optional)\nLisp function, lambda, or Babel block to filter out rows.\n** Available input columns\n  %s\n** Example\n  ~(>= (string-to-number quty) 3)~\n  only rows with cell ~quty~ higher or equal to ~3~ are retained.\n  ~(not (equal tag \\\"dispose\\\"))~\n  rows with cell ~tag~ equal to ~dispose~ are filtered out.\"\n           :post \"* Post-process (optional)\nThe output table may be post-processed prior to printing it\nin the current buffer.\nThe processor may be a Lisp function, a lambda, or a Babel block.\n** Example:\n  ~(lambda (table) (append table '(hline (banana 42))))~\n  two rows are appended at the end of the output table:\n  ~hline~ which means horizontal line,\n  and a row with two cells.\"\n;; bazilo]\n           :file \"* In which file is the table?\nThe table may be in another file.\nLeave answer empty to mean that the table is in the current buffer.\"\n           :precompute \"* Precompute (optional)\nThe input table may be enriched with additional columns prior to aggregating.\nThe syntax is the regular Org table spreadsheet formulas for columns,\nincluding formatting.\nAdditionnaly, the name of new columns can be specified after a semicolumn.\n** Available columns\n  %s\n** Example\n  ~quty*10;f1;'q10'~\nmeans:\n- add a new column to the input table named ~q10~\n- compute it as ~10~ times the ~quty~ input column\n- format it with ~f1~, 1 digit after dot\"\n           :cols \"* Target columns\n** They may be\n- bare input columns, acting as grouping keys,\n- formulas in the syntax of Org spreadsheet, like ~vmean()~, ~vsum()~, ~count()~.\n** Formatting\nEach target column may be followed optionally by semicolon separated parameters:\n- alternate name, example ;'alternate-name'\n- formatting, examples ~;f2~ ~;%%.2f~\n- sorting, examples ~;^a~ ~;^A~ ~;^n~ ~;^N~\n- invisibility  ~;<>~\n** Available input columns\n  %s\n** Examples:\n  ~vmean(quty);f2~, ~vsum(amount);'total'~\"\n           :hline \"* Output horizontal separators level (optional)\n- ~0~ or empty means no lines in the output\n- ~1~ means separate rows of identical values on 1 column\n- ~2~ means separate rows of identical values on 2 columns\n- larger values are allowed, but questionably useful.\nThe columns considered are the sorted ones.\"\n           :cols-tr \"* Target columns\n** Optional\nIf the answer is left empty, all input columns are kept,\nin the same order.\n** Available input columns\n  %s\"\n;; [bazilo synchronize orgtbl-αggregate & orgtbl-joιn\n           ))\n        (main-window (selected-window))\n        (help-window (get-buffer-window \"*orgtbl-aggregate-help*\")))\n    (if help-window\n        (select-window help-window)\n      (setq main-window (split-window nil 16 'above))\n      (switch-to-buffer \"*orgtbl-aggregate-help*\")\n      (setq help-window (selected-window)))\n    (org-mode)\n    (erase-buffer)\n    (insert (apply #'format (plist-get docs explain) args))\n    (goto-char (point-min))\n    (select-window main-window)))\n\n(defun orgtbl-aggregate--wizard-query-table (table expert)\n  \"Query the 4 fields composing a generalized table: file:name:params:slice.\nIt may be only 3 fields in case of orgid:params:slice or\nfile.csv:(csv):slice.\nIf TABLE is not nil, it is decomposed into file:name:params:slice, and each\nof those 4 fields serve as default answer when prompting.\nAlternately, file:name may be orgid, an ID which knows its file location.\nWhen EXPERT is nil, only basic parameters are queried.\nNote that when an expert parameter was set prior to entering the wizard,\nit is queried even when EXPERT is nil.\"\n  (let (file name orgid params slice isorgid)\n    (if table\n        (let ((struct (orgtbl-aggregate--parse-locator table)))\n          (setq file   (aref struct 0))\n          (setq name   (aref struct 1))\n          (setq orgid  (aref struct 2))\n          (setq params (aref struct 3))\n          (setq slice  (aref struct 4))))\n\n    (setq\n     isorgid\n     (cond\n      (orgid t)\n      (name nil)\n      (expert\n       (orgtbl-aggregate--display-help :isorgid)\n       (let ((use-short-answers t))\n         (yes-or-no-p \"Is the input pointed to by an Org Mode ID? \")))\n      (t nil)))\n\n    (if isorgid\n        (progn\n          (orgtbl-aggregate--display-help :orgid)\n          (unless org-id-locations (org-id-locations-load))\n          (setq orgid\n                (completing-read\n                 \"Org ID: \"\n                 (hash-table-keys org-id-locations)\n                 nil\n                 nil ;; user is free to input anything\n                 orgid)))\n\n      (when (or expert file)\n        (orgtbl-aggregate--display-help :file)\n        (let ((insert-default-directory nil))\n          (setq file\n                (orgtbl-aggregate--nil-if-empty\n                 (read-file-name \"File (RET for current buffer): \"\n                                 nil\n                                 nil\n                                 nil\n                                 file)))))\n\n      (orgtbl-aggregate--display-help :name)\n      (setq name\n            (completing-read\n             \"Table or Babel: \"\n             (orgtbl-aggregate--list-local-tables file)\n             nil\n             nil ;; user is free to input anything\n             name)))\n\n    (and\n     file\n     (not params)\n     (cond\n      ((string-match-p (rx \".csv\"  eos) file)\n       (setq params \"(csv)\"))\n      ((string-match-p (rx \".json\" eos) file)\n       (setq params \"(json)\"))))\n\n    (when (or expert params)\n      (orgtbl-aggregate--display-help :params)\n      (setq params\n            (read-string\n             \"Babel parameters (optional): \"\n             params\n             'orgtbl-aggregate-history-cols)))\n\n    (when (or expert slice)\n      (orgtbl-aggregate--display-help :slice)\n      (setq slice\n            (read-string\n             \"Input slicing (optional): \"\n             slice\n             'orgtbl-aggregate-history-cols)))\n\n    (orgtbl-aggregate--assemble-locator file name orgid params slice)))\n\n;; bazilo]\n\n(defun orgtbl-aggregate--wizard-aggregate-create-update (oldline expert)\n  \"Update OLDLINE parameters by interactivly querying user.\nOLDLINE is a plist containing parameter-value pairs.\nExample: \\\\'(:table \\\"thetable\\\" :cols \\\"day vsum(quty)\\\" …)\nOLDLINE is supposed to be extracted from an Org Mode block such as:\n#+begin: aggregate :table \\\"thetable\\\" :cols \\\"day vsum(quty)\\\" …\nIf (point) is not on such a line, OLDLINE is nil.\nThe function returns a plist which is an updated version of OLDLINE\namended by the user.\nWhen EXPERT is nil, only basic parameters are queried.\nNote that when an expert parameter was set prior to entering the wizard,\nit is queried even when EXPERT is nil.\"\n  (let ((minibuffer-local-completion-map\n         (define-keymap :parent minibuffer-local-completion-map\n           \"SPC\" nil)) ;; allow inserting spaces\n        table headerlist header precompute\n        aggcols aggcond hline postprocess params)\n\n    (save-window-excursion\n      (setq table\n            (orgtbl-aggregate--wizard-query-table\n             (orgtbl-aggregate--plist-get-remove oldline :table)\n             expert))\n\n      (setq headerlist\n            (orgtbl-aggregate--get-header-table table))\n\n      (setq header\n            (mapconcat\n             (lambda (x) (format \" ~%s~\" x))\n             headerlist))\n\n      (setq precompute (orgtbl-aggregate--plist-get-remove oldline :precompute))\n      (when (or expert precompute)\n        (orgtbl-aggregate--display-help :precompute header)\n        (setq precompute\n              (read-string\n               \"Formulas for additional input columns (optional): \"\n               precompute\n               'orgtbl-aggregate-history-cols)))\n\n      (when (orgtbl-aggregate--nil-if-empty precompute)\n        (setq headerlist\n              (append headerlist\n                      (cl-loop\n                       for pair in\n                       (orgtbl-aggregate--parse-preprocess\n                        precompute\n                        (length headerlist))\n                       collect (cdr pair))))\n        (setq header\n              (mapconcat\n               (lambda (x) (format \" ~%s~\" x))\n               headerlist)))\n\n      (orgtbl-aggregate--display-help :cols header)\n      (setq aggcols\n            (replace-regexp-in-string\n             \"\\\"\" \"'\"\n             (read-string\n              \"Target columns & formulas: \"\n              (orgtbl-aggregate--merge-list-into-single-string\n               (orgtbl-aggregate--plist-get-remove oldline :cols))\n              'orgtbl-aggregate-history-cols)))\n\n      (setq aggcond (orgtbl-aggregate--plist-get-remove oldline :cond))\n      (when (or expert aggcond)\n        (orgtbl-aggregate--display-help :cond header)\n        (setq aggcond\n              (read-string\n               \"Row filter (optional): \"\n               (and aggcond (format \"%s\" aggcond))\n               'orgtbl-aggregate-history-cols)))\n\n      (setq hline (orgtbl-aggregate--plist-get-remove oldline :hline))\n      (when (or expert hline)\n        (orgtbl-aggregate--display-help :hline)\n        (setq hline\n              (completing-read\n               \"hline (optional): \"\n               '(\"0\" \"1\" \"2\" \"3\")\n               nil\n               'confirm\n               (orgtbl-aggregate--cell-to-string hline))))\n\n      (setq postprocess (orgtbl-aggregate--plist-get-remove oldline :post))\n      (when (or expert postprocess)\n        (orgtbl-aggregate--display-help :post)\n        (setq postprocess\n              (read-string\n               \"Post process (optional): \"\n               postprocess\n               'orgtbl-aggregate-history-cols)))\n      )\n\n    (setq params\n          (list\n           :name \"aggregate\"\n           :table table\n           :cols aggcols))\n    (if (orgtbl-aggregate--nil-if-empty aggcond)\n        (nconc params `(:cond ,(read aggcond))))\n    (if (orgtbl-aggregate--nil-if-empty hline)\n        (nconc params `(:hline ,hline)))\n    (if (orgtbl-aggregate--nil-if-empty precompute)\n        (nconc params `(:precompute ,precompute)))\n    (if (orgtbl-aggregate--nil-if-empty postprocess)\n        (nconc params `(:post ,postprocess)))\n\n    ;; recover parameters not taken into account by the wizard\n    (cl-loop\n     for pair on oldline\n     if (car pair)\n     do (nconc params `(,(car pair) ,(cadr pair)))\n     do (setq pair (cdr pair)))\n    params))\n\n;; [bazilo synchronize orgtbl-αggregate & orgtbl-joιn\n\n;;;###autoload\n(defun orgtbl-aggregate-insert-dblock-aggregate (&optional expert)\n  \"Wizard to interactively insert a dynamic aggregated block.\nWhen EXPERT is nil, only basic parameters are queried.\nNote that when an expert parameter was set prior to entering the wizard,\nit is queried even when EXPERT is nil.\"\n  (interactive \"P\")\n  (let* ((oldline (orgtbl-aggregate--parse-header-arguments \"aggregate\"))\n         (params\n          (save-excursion (orgtbl-aggregate--wizard-aggregate-create-update oldline expert))))\n    (if (not oldline)\n        (org-create-dblock params)\n      (beginning-of-line)\n      (kill-line)\n      (org-create-dblock params)\n      (delete-blank-lines)\n      (forward-line 1)\n      (kill-line 1)\n      (forward-line -1)\n      (delete-blank-lines))\n    (org-update-dblock)))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Unfold, Fold\n;; Experimental\n;; Typing TAB on a line like\n;; #+begin aggregate params…\n;; unfolds the parameters: a new line for each parameter\n;; and a dedicated help & completion for each activated by TAB\n\n;;;###autoload\n(defun orgtbl-aggregate-dispatch-TAB ()\n  \"Type TAB on a line like #+begin: aggregate to activate custom functions.\nActually, any line following this pattern will do:\n#+xxxxx: yyyyy\nTyping TAB will dispatch to function org-TAB-xxxxx-yyyyy if it exists.\nIf it does not exist, Org Mode will proceed as usual.\nIf it exists and returns nil, Org Mode will proceed as usual as well.\nIt it returns non-nil, the TAB processing will stop there.\"\n  (save-excursion\n    (if (and\n         (not (bolp))\n         (progn\n           (end-of-line)\n           (not (org-fold-core-folded-p)))\n         (progn\n           (beginning-of-line)\n           (re-search-forward\n            (rx\n             point\n             \"#+\"\n             (group (+ (any \"a-z0-9_-\")))\n             \":\"\n             (* blank)\n             (group (+ (any \":a-z0-9_-\")))\n             (* blank))\n            nil t)))\n        (let ((symb\n               (intern\n                (format\n                 \"org-TAB-%s-%s\"\n                 (downcase (match-string-no-properties 1))\n                 (downcase (match-string-no-properties 2))))))\n          (if (symbol-function symb)\n              (funcall symb))))))\n\n(defun org-TAB-begin-aggregate ()\n  \"Dispatch to unfolding or folding code.\nIf the line following\n  #+begin: aggregate\nis an unfoled one of the form:\n  #+aggregate: …\nthen proceed to folding, otherwise unfold.\"\n  (if (save-excursion\n        (forward-line 1)\n        (beginning-of-line)\n        (let ((case-fold-search t))\n          (re-search-forward\n           (rx point \"#+aggregate:\")\n           nil t)))\n      (org-TAB-begin-aggregate-fold)\n    (org-TAB-begin-aggregate-unfold)))\n\n(defun orgtbl-aggregate--insert-remove-pair-from-alist (tag alist)\n  \"Helper function for folding a pair (TAG . VALUE) in ALIST.\"\n  (let ((value\n         (orgtbl-aggregate--nil-if-empty\n          (orgtbl-aggregate--alist-get-remove tag alist))))\n    (if value\n        (insert\n         (format \" %s %s\"\n                 tag\n                 (prin1-to-string value))))))\n\n(defun orgtbl-aggregate-get-all-unfolded ()\n  \"Prepare an a-list of all unfolded parameters.\"\n  (interactive)\n  (save-excursion\n    (re-search-backward (rx bol \"#+begin:\") nil t)\n    (cl-loop\n     do (forward-line 1)\n     while\n     (let ((case-fold-search t))\n       (re-search-forward\n        (rx point \"#+aggregate:\" (* blank)\n            (group (+ (any \":a-z0-9_-\")))\n            (* blank)\n            (group (* nonl)))\n        nil t))\n     collect\n     (cons\n      (intern (match-string-no-properties 1))\n      (match-string-no-properties 2)))))\n\n(defun orgtbl-aggregate--TAB-replace-value (getter)\n  \"Update a #+aggregate: line\nfrom\n  #+aggregate: :tag OLD\nto\n  #+aggregate: :tag NEW\nNEW being the result of executing (GETTER OLD)\"\n  (let* ((start (point))\n         (end (pos-eol))\n         (new\n          (funcall\n           getter\n           (buffer-substring-no-properties start end))))\n    (when new\n      (delete-region start end)\n      (delete-horizontal-space)\n      (insert \" \" new))))\n\n;; bazilo]\n\n(defun org-TAB-begin-aggregate-fold ()\n  \"Turn all lines of the form #+aggregate: … into a single line.\nThat is, fold the may lines of the form:\n  #+aggregate: param…\ninto the single line of the form:\n  #+begin: aggregate params…\nNote that the resulting :table XXX parameter is composed of several\nindividual parameters.\"\n  (orgtbl-aggregate--dismiss-help)\n  (let* ((alist (orgtbl-aggregate-get-all-unfolded)))\n    (end-of-line)\n    (insert\n     \" :table \\\"\"\n     (orgtbl-aggregate--assemble-locator\n      (orgtbl-aggregate--alist-get-remove :file   alist)\n      (orgtbl-aggregate--alist-get-remove :name   alist)\n      (orgtbl-aggregate--alist-get-remove :orgid  alist)\n      (orgtbl-aggregate--alist-get-remove :params alist)\n      (orgtbl-aggregate--alist-get-remove :slice  alist))\n     \"\\\"\")\n    (orgtbl-aggregate--insert-remove-pair-from-alist :precompute alist)\n    (orgtbl-aggregate--insert-remove-pair-from-alist :cols       alist)\n    (orgtbl-aggregate--insert-remove-pair-from-alist :cond       alist)\n    (orgtbl-aggregate--insert-remove-pair-from-alist :hline      alist)\n    (orgtbl-aggregate--insert-remove-pair-from-alist :post       alist)\n    (cl-loop\n     for pair in alist\n     if (car pair)\n     do (orgtbl-aggregate--insert-remove-pair-from-alist (car pair) alist))\n    (forward-line 1)\n    (while\n        (let ((case-fold-search t))\n          (beginning-of-line)\n          (re-search-forward (rx point \"#+aggregate:\") nil t))\n      (beginning-of-line)\n      (delete-line))\n    (forward-line -1)\n    t))\n\n(defun org-TAB-begin-aggregate-unfold ()\n  \"Turn the single line #+begin: aggregate into several lines.\nThat is, move all parameters in the line\n  #+begin: aggregate params…\ninto several lines, each with a single parameter.\nNote that the :table XXX parameter is decomposed into several\nindividual parameter for an easier reading.\"\n  (let* ((line (orgtbl-aggregate--parse-header-arguments \"aggregate\"))\n         (point (progn (end-of-line) (point)))\n         (struct (orgtbl-aggregate--parse-locator\n                  (orgtbl-aggregate--plist-get-remove line :table))))\n    (insert \"\\n#+aggregate: :file \"   (or (aref struct 0) \"\"))\n    (insert \"\\n#+aggregate: :name \"   (or (aref struct 1) \"\"))\n    (insert \"\\n#+aggregate: :orgid \"  (or (aref struct 2) \"\"))\n    (insert \"\\n#+aggregate: :params \" (or (aref struct 3) \"\"))\n    (insert \"\\n#+aggregate: :slice \"  (or (aref struct 4) \"\"))\n    (insert \"\\n#+aggregate: :precompute \"\n            (or (orgtbl-aggregate--plist-get-remove line :precompute) \"\"))\n    (insert \"\\n#+aggregate: :cols \"\n            (orgtbl-aggregate--merge-list-into-single-string\n             (or (orgtbl-aggregate--plist-get-remove line :cols ) \"\")))\n    (insert \"\\n#+aggregate: :cond \"\n            (format \"%s\" (or (orgtbl-aggregate--plist-get-remove  line :cond ) \"\")))\n    (insert \"\\n#+aggregate: :hline \"\n            (format \"%s\" (or (orgtbl-aggregate--plist-get-remove  line :hline) \"\")))\n    (insert \"\\n#+aggregate: :post \"\n            (format \"%s\" (or (orgtbl-aggregate--plist-get-remove  line :post ) \"\")))\n    (cl-loop\n     for pair on line\n     if (car pair)\n     do (insert (format \"\\n#+aggregate: %s %s\" (car pair) (cadr pair)))\n     do (setq pair (cdr pair)))\n    (goto-char point)\n    (beginning-of-line)\n    (forward-word 2)\n    (delete-region (point) point)\n    t))\n\n(defun orgtbl-aggregate--column-names-from-unfolded ()\n  \"Return a textual list of column names.\nThey are computed by looking at the distant table\n(an Org table, a Babel block, a CSV, or a JSON)\nand recovering its header if any.\nIf there is no header, $1 $2 $3... is returned.\"\n  (let*\n      ((alist (orgtbl-aggregate-get-all-unfolded))\n       (table\n        (orgtbl-aggregate--assemble-locator\n         (alist-get :file   alist)\n         (alist-get :name   alist)\n         (alist-get :orgid  alist)\n         (alist-get :params alist)\n         (alist-get :slice  alist))))\n    (mapconcat\n     (lambda (x) (format \" ~%s~\" x))\n     (orgtbl-aggregate--get-header-table table))))\n\n;; [bazilo synchronize orgtbl-αggregate & orgtbl-joιn\n\n(defun org-TAB-aggregate-:file ()\n  \"Provide help and completion for the #+aggregate: file XXX parameter.\"\n  (orgtbl-aggregate--display-help :file)\n  (orgtbl-aggregate--TAB-replace-value\n   (lambda (old)\n     (read-file-name\n      \"File: \"\n      (file-name-directory    old)\n      nil\n      nil\n      (file-name-nondirectory old)))))\n\n(defun org-TAB-aggregate-:name ()\n  \"Provide help and completion for the #+aggregate: name XXX parameter.\"\n  (orgtbl-aggregate--display-help :name)\n  (orgtbl-aggregate--TAB-replace-value\n   (lambda (old)\n     (completing-read\n      \"Table or Babel name: \"\n      (orgtbl-aggregate--list-local-tables\n       (orgtbl-aggregate--nil-if-empty\n        (alist-get :file (orgtbl-aggregate-get-all-unfolded))))\n      nil\n      nil ;; user is free to input anything\n      old))))\n\n(defun org-TAB-aggregate-:orgid ()\n  \"Provide help and completion for the #+aggregate: id XXX parameter.\"\n  (orgtbl-aggregate--display-help :orgid)\n  (unless org-id-locations (org-id-locations-load))\n  (orgtbl-aggregate--TAB-replace-value\n   (lambda (old)\n     (completing-read\n      \"Org-ID: \"\n      (hash-table-keys org-id-locations)\n      nil\n      nil ;; user is free to input anything\n      old))))\n\n(defun org-TAB-aggregate-:params ()\n  (orgtbl-aggregate--display-help :params))\n\n(defun org-TAB-aggregate-:slice ()\n  (orgtbl-aggregate--display-help :slice))\n\n(defun org-TAB-aggregate-:cols ()\n  (orgtbl-aggregate--display-help :cols\n   (orgtbl-aggregate--column-names-from-unfolded)))\n\n(defun org-TAB-aggregate-:cond ()\n  (orgtbl-aggregate--display-help :cond\n   (orgtbl-aggregate--column-names-from-unfolded)))\n\n(defun org-TAB-aggregate-:post ()\n  (orgtbl-aggregate--display-help :post))\n\n;; bazilo]\n\n(defun org-TAB-aggregate-:precompute ()\n  (orgtbl-aggregate--display-help :precompute\n   (orgtbl-aggregate--column-names-from-unfolded)))\n\n(defun org-TAB-aggregate-:hline ()\n  \"Hitting TAB on #+aggregate: hline N cycles the parameter value.\nThe cycle is\nnothing → 1 → 2 → 3 → nothing.\"\n  (orgtbl-aggregate--display-help :hline)\n  (orgtbl-aggregate--TAB-replace-value\n   (lambda (old)\n     (cond\n      ((equal old \"\" ) \"1\")\n      ((equal old \"1\") \"2\")\n      ((equal old \"2\") \"3\")\n      ((equal old \"3\") \"\" )\n      (t \"\")))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; The Transposition package\n\n(defun orgtbl-aggregate--create-table-transposed (table cols aggcond)\n  \"Convert the source TABLE to a tranposed version.\nTABLE is a list of lists of cells.\nCOLS gives the source columns that should become rows.\nIf COLS is nil, all source columns are taken.\nAGGCOND is a Lisp expression given bu the user.  It is evaluated\nagainst each row.  If the result is nil, the row is ignored.\nIf AGGCOND is nil, all source rows are taken.\"\n  (if (stringp cols)\n      (setq cols (orgtbl-aggregate--split-string-with-quotes cols)))\n  (setq cols\n        (if cols\n\t    (cl-loop for column in cols\n\t\t     collect\n\t\t     (orgtbl-aggregate--colname-to-int column table t))\n          (let ((head table))\n\t    (orgtbl-aggregate--pop-leading-hline head)\n\t    (cl-loop for _x in (car head)\n\t\t     for i from 1\n\t\t     collect i))))\n  (if aggcond\n      (setq aggcond\n            (orgtbl-aggregate--replace-colnames-nth table aggcond)))\n  (let ((result (cl-loop for _x in cols collect (list t)))\n        ;; '(t) or `(t) would be incorrect╶────────▷─╯\n        (nhline 0))\n    (cl-loop for row in table\n             for rownum from 0\n\t     do\n\t     (if (eq row 'hline)\n\t\t (setq nhline (1+ nhline))\n\t       (setq row (cons nhline row)))\n\t     do\n\t     (when (or (eq row 'hline) (not aggcond) (eval aggcond))\n\t       (cl-loop\n\t\tfor spec in cols\n\t\tfor r in result\n\t\tdo\n\t\t(nconc\n                 r\n                 (list\n                  (cond\n                   ((eq row 'hline) \"\")\n                   ((eq spec 0) rownum)\n                   ((>= spec (length row)) (nth 0 row))\n                   (t (nth spec row))))))))\n    (cl-loop for row in result\n\t     do (orgtbl-aggregate--pop-simple row)\n\t     collect\n\t     (if (cl-loop for x in row\n\t\t\t  always (equal \"\" x))\n\t\t 'hline\n\t       row))))\n\n;;;###autoload\n(defun orgtbl-to-transposed-table (table params)\n  \"Convert the Org Mode TABLE to a transposed version.\nRows become columns, columns become rows.\n\nThe source table must contain sending directives with\nthe following format:\n#+ORGTBL: SEND destination orgtbl-to-transposed-table :cols ... :cond ...\n\nPARAMS are the user given parameters found in the #+ORGTBL: SEND line\n\nThe destination must be specified somewhere in the same file\nwith a bloc like this:\n  #+BEGIN RECEIVE ORGTBL destination\n  #+END RECEIVE ORGTBL destination\n\n:cols     optional, if omitted all source columns are taken.\n          Columns specified here will become rows in the result.\n          Valid specifications are\n          - names as they appear in the first row of the source table\n          - $N forms, starting from $1\n          - the special hline column which is the numbering of\n            blocks separated by horizontal lines in the source table\n\n:cond     optional\n          a lisp expression to filter out rows in the source table\n          when the expression evaluate to nil for a given row of\n          the source table, then this row is discarded in the\n          resulting table.\n          Example:\n             (equal Q \\\"b\\\")\n          Which means: keep only source rows for which the column Q\n          has the value b\n\nColumns in the source table may be in the dollar form,\nfor example $3 to name the 3th column,\nor by its name if the source table have a header.\nIf all column names are in the dollar form,\nthe table is supposed not to have a header.\nThe special column name \\\"hline\\\" takes values from zero and up\nand is incremented by one for each horizontal line.\n\nHorizontal lines are converted to empty columns,\nand the other way around.\n\nThe destination must be specified somewhere in the same file\nwith a block like this:\n  #+BEGIN RECEIVE ORGTBL destination_table_name\n  #+END RECEIVE ORGTBL destination_table_name\n\nType \\\\<org-mode-map> & \\\\[org-ctrl-c-ctrl-c] in the source\ntable to re-create the transposed version.\n\nNote:\n This is the \\\"push\\\" mode for transposing a table.\n To use the \\\"pull\\\" mode, look at the org-dblock-write:transpose function.\n\nNote:\n The name `orgtbl-to-transposed-table' follows the Org Mode standard\n with functions like `orgtbl-to-csv', `orgtbl-to-html'...\"\n  (interactive)\n  (orgtbl-aggregate--elisp-table-to-string\n   (orgtbl-aggregate--post-process\n    (orgtbl-aggregate--create-table-transposed\n     table\n     (plist-get params :cols)\n     (plist-get params :cond))\n    (plist-get params :post))))\n\n;;;###autoload\n(defun org-dblock-write:transpose (params)\n  \"Create a transposed version of an Org Mode table.\nRows become columns, columns become rows.\n\nPARAMS are the user given parameters found in the\n#+BEGIN: transpose line\n\n:table    names the source table\n\n:cols     optional, if omitted all source columns are taken.\n          Columns specified here will become rows in the result.\n          Valid specifications are\n          - names as they appear in the first row of the source table\n          - $N forms, starting from $1\n          - the special hline column which is the numbering of\n            blocks separated by horizontal lines in the source table.\n\n:cond     optional\n          a Lisp expression to filter out rows in the source table\n          when the expression evaluate to nil for a given row of the\n          source table, then this row is discarded in the resulting table.\n          Example:\n             (equal q \\\"b\\\")\n          Which means: keep only source rows for which the column q\n          has the value b.\n\nColumns in the source table may be in the dollar form,\nfor example $3 to name the 3th column,\nor by its name if the source table have a header.\nIf all column names are in the dollar form,\nthe table is supposed not to have a header.\nThe special column name \\\"hline\\\" takes values from zero and up\nand is incremented by one for each horizontal line.\n\nHorizontal lines are converted to empty columns,\nand the other way around.\n\n- Create an empty dynamic block like this:\n  #+BEGIN: transpose :table originaltable\n  #+END\n- Type \\\\<org-mode-map> & \\\\[org-ctrl-c-ctrl-c] over the BEGIN line\n  this fills in the block with the transposed table\n\nNote:\n This is the \\\"pull\\\" mode for transposing a table.\n To use the \\\"push\\\" mode, look at the orgtbl-to-transposed-table function.\n\nNote:\n The name `org-dblock-write:transpose' is constrained\n by the `org-update-dblock' function.\"\n  (interactive)\n  (let ((formula (plist-get params :formula))\n\t(content (plist-get params :content))\n\t(post    (plist-get params :post)))\n    (if content\n\t(let ((case-fold-search t))\n\t  (string-match\n\t   (rx bos\n               (* (* blank) \"\\n\")\n               (group (* (* blank) (? \"#+\" (* nonl)) \"\\n\")))\n\t   content)\n          (insert\n           (replace-regexp-in-string\n            (rx bol (* (or blank \"\\n\")) eos)\n            \"\"\n            (replace-regexp-in-string\n             (rx bol \"#+tblfm\" (* (or any \"\\n\")) eos)\n             \"\"\n             (match-string 1 content))))))\n    (orgtbl-aggregate--insert-elisp-table\n     (orgtbl-aggregate--post-process\n      (orgtbl-aggregate--create-table-transposed\n       (orgtbl-aggregate--remove-cookie-lines\n        (orgtbl-aggregate-table-from-any-ref (plist-get params :table)))\n       (plist-get params :cols)\n       (plist-get params :cond))\n      post))\n    (orgtbl-aggregate--table-recalculate content formula)))\n\n(defun orgtbl-aggregate--wizard-transpose-create-update (oldline expert)\n  \"Update OLDLINE parameters by interactivly querying user.\nOLDLINE is a plist containing parameter-value pairs.\nExample: \\\\'(:table \\\"thetable\\\" :cols \\\"day month\\\" …)\nOLDLINE is supposed to be extracted from an Org Mode block such as:\n#+begin: transpose :table \\\"thetable\\\" :cols \\\"day month\\\" …\nIf (point) is not on such a line, OLDLINE is nil.\nThe function returns a plist which is an updated version of OLDLINE\namended by the user.\nWhen EXPERT is nil, only basic parameters are queried.\nNote that when an expert parameter was set prior to entering the wizard,\nit is queried even when EXPERT is nil.\"\n  (let ((minibuffer-local-completion-map\n         (define-keymap :parent minibuffer-local-completion-map\n           \"SPC\" nil)) ;; allow inserting spaces\n        table headerlist header aggcols aggcond postprocess params)\n\n    (save-window-excursion\n      (setq table\n            (orgtbl-aggregate--wizard-query-table\n             (orgtbl-aggregate--plist-get-remove oldline :table)\n             expert))\n\n      (setq headerlist\n            (orgtbl-aggregate--get-header-table table))\n\n      (setq header\n            (mapconcat\n             (lambda (x) (format \" ~%s~\" x))\n             headerlist))\n\n      (orgtbl-aggregate--display-help :cols-tr header)\n      (setq aggcols\n            (replace-regexp-in-string\n             \"\\\"\" \"'\"\n             (read-string\n              \"Target columns & formulas: \"\n              (orgtbl-aggregate--merge-list-into-single-string\n               (orgtbl-aggregate--plist-get-remove oldline :cols))\n              'orgtbl-aggregate-history-cols)))\n\n      (setq aggcond (orgtbl-aggregate--plist-get-remove oldline :cond))\n      (when (or expert aggcond)\n        (orgtbl-aggregate--display-help :cond header)\n        (setq aggcond\n              (read-string\n               \"Row filter (optional): \"\n               aggcond\n               'orgtbl-aggregate-history-cols)))\n\n      (setq postprocess (orgtbl-aggregate--plist-get-remove oldline :post))\n      (when (or expert postprocess)\n        (orgtbl-aggregate--display-help :post)\n        (setq postprocess\n              (read-string\n               \"Post process (optional): \"\n               postprocess\n               'orgtbl-aggregate-history-cols)))\n      )\n\n    (setq params\n          (list\n           :name \"transpose\"\n           :table table\n           :cols aggcols))\n    (unless (eq (length aggcond) 0)\n      (nconc params `(:cond ,(read aggcond))))\n    (unless (eq (length postprocess) 0)\n      (nconc params `(:post ,postprocess)))\n\n    ;; recover parameters not taken into account by the wizard\n    (cl-loop\n     for pair on oldline\n     if (car pair)\n     do (nconc params `(,(car pair) ,(cadr pair)))\n     do (setq pair (cdr pair)))\n    params))\n\n;;;###autoload\n(defun orgtbl-aggregate-insert-dblock-transpose (&optional expert)\n  \"Wizard to interactively insert a transpose dynamic block.\"\n  (interactive \"P\")\n  (let* ((oldline (orgtbl-aggregate--parse-header-arguments \"transpose\"))\n         (params\n          (save-excursion (orgtbl-aggregate--wizard-transpose-create-update oldline expert)))\n         tblfm)\n    (when oldline\n      (org-mark-element)\n      (setq tblfm\n            (orgtbl-aggregate--recover-TBLFM\n             (buffer-substring-no-properties\n              (region-beginning) (1- (region-end)))))\n      (delete-region (region-beginning) (1- (region-end))))\n    (org-create-dblock params)\n    (when tblfm\n      (forward-line 1)\n      (insert \"\\n\" tblfm)\n      (forward-line -2))\n    (org-update-dblock)))\n\n;; [bazilo synchronize orgtbl-αggregate & orgtbl-joιn\n\n;; Insert a dynamic bloc with the C-c C-x x dispatcher\n;; and activate TAB on #+begin: aggregate ...\n;;;###autoload\n(eval-after-load 'org\n  '(progn\n     ;; org-dynamic-block-define found in Emacs 27.1\n     (org-dynamic-block-define \"aggregate\" #'orgtbl-aggregate-insert-dblock-aggregate)\n     (org-dynamic-block-define \"transpose\" #'orgtbl-aggregate-insert-dblock-transpose)))\n\n;; This hook will only work if orgtbl-aggregate is loaded,\n;; thus the eval-after-load 'orgtbl-aggregate\n;; We do not want this hook to be added to Org Mode if orgtbl-aggregate\n;; is not used, thus the eval-after-load 'orgtbl-aggregate\n;;;###autoload\n(eval-after-load 'orgtbl-aggregate\n  '(add-hook 'org-cycle-tab-first-hook #'orgtbl-aggregate-dispatch-TAB))\n\n;; bazilo]\n\n(provide 'orgtbl-aggregate)\n;;; orgtbl-aggregate.el ends here\n"
  },
  {
    "path": "orgtbl-aggregate.info",
    "content": "This is orgtbl-aggregate.info, produced by makeinfo version 6.8 from\norgtbl-aggregate.texi.\n\n\nINFO-DIR-SECTION Emacs\nSTART-INFO-DIR-ENTRY\n* Orgtbl Aggregate: (orgtbl-aggregate).     Create an aggregated Org table from another one\nEND-INFO-DIR-ENTRY\n\nINFO-DIR-SECTION Misc\nSTART-INFO-DIR-ENTRY\n* (orgtbl-aggregate).   Aggregate Values in a Table.\nEND-INFO-DIR-ENTRY\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Top,  Next: New,  Up: (dir)\n\nAggregate Values in a Table\n***************************\n\nAggregating a table is creating a new table by computing sums, averages,\nand so on, out of material from the first table.\n\n* Menu:\n\n* New::\n* Examples::\n* Stop reading here! 80/20::\n* Equivalent in SQL, R, Datamash, el-tblfn, Awk, C++: Equivalent in SQL R Datamash el-tblfn Awk C++.\n* Wizards::\n* Thecols parameter: The cols parameter.\n* Column names::\n* Formatters::\n* Sorting::\n* hlines in the output table::\n* Cells processing::\n* Wide variety of inputs::\n* Post-processing::\n* Pull & Push::\n* Debugging::\n* Tricks::\n* Installation::\n* Authors, contributors: Authors contributors.\n* Changes::\n* GPL 3 License::\n\n— The Detailed Node Listing —\n\nExamples\n\n* A very simple example::\n* Demonstrate sum and average computing::\n* Example without days::\n* Example of counting each combination::\n\nStop reading here! 80/20\n\n* Name your input table::\n* Create an aggregation block::\n* Refresh the aggregation::\n\nEquivalent in SQL, R, Datamash, el-tblfn, Awk, C++\n\n* SQL equivalent::\n* R equivalent::\n* Datamash equivalent::\n* el-tblfn::\n* Awk equivalent::\n* C++ equivalent::\n\nWizards\n\n* Guiding (traditional) wizard::\n* Experimental free form wizard::\n\nThe :cols parameter\n\n* Names of input columns::\n* Grouping specifications incols: Grouping specifications in cols.\n* The hline column::\n* The @# column::\n* Aggregation formulas incols: Aggregation formulas in cols.\n* Correlation of two columns::\n* (Almost) any expression can be specified: [Almost) any expression can be specified.\n\nColumn names\n\n* Input table with or without a header::\n* Column names of the input table::\n* Multiple lines header::\n* Custom column names::\n\nFormatters\n\n* Org Mode compatible formatters::\n* Debugging formatters::\n* Discarding an output column::\n\nSorting\n\n* Example with one sorting column::\n* Several sorting columns::\n\nhlines in the output table\n\n* Output hlines depends on sorting columns::\n* Example with hline 2::\n\nCells processing\n\n* Where Calc interpretation happens?::\n* Dates::\n* Durations::\n* Empty and malformed input cells::\n* Symbolic computation::\n* Intervals::\n* Error or precision forms::\n\nWide variety of inputs\n\n* Standard Org Mode input::\n* Virtual input table from Babel::\n* An Org ID::\n* CSV input::\n* JSON input::\n* Input slicing::\n* Thecond filter: The cond filter.\n* Virtual input columns::\n\nPost-processing\n\n* Spreadsheet formulas::\n* Algorithm post processing::\n* Grand total::\n* Chaining::\n\nPull & Push\n\n* Pull mode::\n* Push mode::\n* Pull or push ?::\n\nDebugging\n\n* Seeing the $ forms::\n* Seeing Calc formulas before evaluation::\n* Seeing Lisp internal form of Calc formulas::\n* Example of debugging vsum(nn^2)::\n* Summary of debugging formatters::\n\nTricks\n\n* Sorting: Sorting (1).\n* A few lowest or highest values::\n* Span of values::\n* No aggregation::\n\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: New,  Next: Examples,  Prev: Top,  Up: Top\n\n1 New\n*****\n\nTranspose-babel blocks now handle ‘@#’ and ‘hline’ special columns.\n‘@#’ is the input table row number.  ‘hline’ is the block number between\ntwo horizontal lines where the current row is located.\n\n   And by the way, yes, ‘orgtbl-aggregate’ comes with ‘orgtbl-transpose’\nas a bonus.  It flips rows with columns.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Examples,  Next: Stop reading here! 80/20,  Prev: New,  Up: Top\n\n2 Examples\n**********\n\n* Menu:\n\n* A very simple example::\n* Demonstrate sum and average computing::\n* Example without days::\n* Example of counting each combination::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: A very simple example,  Next: Demonstrate sum and average computing,  Up: Examples\n\n2.1 A very simple example\n=========================\n\nWe have a table of activities and quantities (whatever they are) over\nseveral days.\n\n     #+name: original\n     | Day       | Color | Level | Quantity |\n     |-----------+-------+-------+----------|\n     | Monday    | Red   |    30 |       11 |\n     | Monday    | Blue  |    25 |        3 |\n     | Tuesday   | Red   |    51 |       12 |\n     | Tuesday   | Red   |    45 |       15 |\n     | Tuesday   | Blue  |    33 |       18 |\n     | Wednesday | Red   |    27 |       23 |\n     | Wednesday | Blue  |    12 |       16 |\n     | Wednesday | Blue  |    15 |       15 |\n     | Thursday  | Red   |    39 |       24 |\n     | Thursday  | Red   |    41 |       29 |\n     | Thursday  | Red   |    49 |       30 |\n     | Friday    | Blue  |     7 |        5 |\n     | Friday    | Blue  |     6 |        8 |\n     | Friday    | Blue  |    11 |        9 |\n\n   To begin with we want to gather all colors and count how many times\nthey appear.  We are interested only in the second column named ‘Color’\n\n   First we give a name to the table through the ‘#+NAME:’ or\n‘#+TBLNAME:’ tags, just above the table.  Then we create a _dynamic\nblock_ to receive the aggregation:\n\n     desired output columns╶──────────────────────────╮\n     the input table╶──────────────╮                  │\n     type of processing╶─╮         │                  │\n                 ╭───────╯         │                  │\n                 ▼             ╭───┴────╮       ╭─────┴───────╮\n     #+begin: aggregate :table \"original\" :cols \"Color count()\"\n     #+end:\n\n   Now typing ‘C-c C-c’ in the dynamic block counts the colors in the\noriginal table:\n\n     C-c C-c here to refresh╶╮\n                  ╭──────────╯\n                  ▼\n     #+begin: aggregate :table \"original\" :cols \"Color count()\"\n     | Color | count() |\n     |-------+---------|\n     | Red   |       7 |\n     | Blue  |       7 |\n     #+end:\n\n   OrgAggregate found two colors, ‘Red’ and ‘Blue’.  It found 7\noccurrences for each.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Demonstrate sum and average computing,  Next: Example without days,  Prev: A very simple example,  Up: Examples\n\n2.2 Demonstrate sum and average computing\n=========================================\n\nNow we want to aggregate this table for each day (because several rows\nexist for each day).  We want the average value of the ‘Level’ column\nfor each day, and the sum of the ‘Quantity’ column.  We write down the\nblock specifying that (later we will see how to automate the creation of\nsuch a block with a *note wizard: Wizards.):\n\n     sum aggregation╶─────────────────────────────────────────────────╮\n     average aggregation╶───────────────────────────────╮             │\n     key grouping column╶───────────────────────╮       │             │\n                                               ╭┴╮ ╭────┴─────╮ ╭─────┴──────╮\n     #+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n     #+end\n\n   Typing ‘C-c C-c’ in the dynamic block computes the aggregation:\n\n     C-c C-c here to refresh╶╮\n                  ╭──────────╯\n                  ▼\n     #+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n                                               ╰┬╯ ╰────┬─────╯ ╰──────┬─────╯\n        ╭───────────────────────────────────────╯       │              │\n        │               ╭───────────────────────────────╯              │\n        │               │               ╭──────────────────────────────╯\n       ╭┴╮         ╭────┴─────╮   ╭─────┴──────╮\n     | Day       | vmean(Level) | vsum(Quantity) |\n     |-----------+--------------+----------------|\n     | Monday    |         27.5 |             14 |\n     | Tuesday   |           43 |             45 |\n     | Wednesday |           18 |             54 |\n     | Thursday  |           43 |             83 |\n     | Friday    |            8 |             22 |\n     #+end\n\n   The source table is not changed in any way.\n\n   To get this result, we specified columns in this way, after the\n‘:cols’ parameter:\n\n   • ‘Day’ : we got the same column as in the source table, except\n     entries are not duplicated.  Here ‘Day’ acts as a _key grouping\n     column_.  We may specify as many key columns as we want just by\n     naming them.  We get only one aggregated row for each different\n     combination of values of key grouping columns.\n\n   • ‘vmean(Level)’ : this instructs OrgAggregate to compute the average\n     of values found in the ‘Level’ column, grouped by the same ‘Day’.\n\n   • ‘vsum(Quantity)’: OrgAggregate computes the sum of values found in\n     the ‘Quantity’ column, one sum for each ‘Day’.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Example without days,  Next: Example of counting each combination,  Prev: Demonstrate sum and average computing,  Up: Examples\n\n2.3 Example without days\n========================\n\nMaybe we are just interested in the sum of ‘Quantities’, regardless of\n‘Days’.  We just type:\n\n     C-c C-c here to refresh╶╮\n                  ╭──────────╯\n                  ▼\n     #+begin: aggregate :table \"original\" :cols \"vsum(Quantity)\"\n                                                 ╰─────┬──────╯\n            ╭──────────────────────────────────────────╯\n       ╭────┴───────╮\n     | vsum(Quantity) |\n     |----------------|\n     |            218 |\n     #+end\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Example of counting each combination,  Prev: Example without days,  Up: Examples\n\n2.4 Example of counting each combination\n========================================\n\nwe may want to count the number of rows for each combination of ‘Day’\nand ‘Color’:\n\n     C-c C-c here to refresh╶╮\n                  ╭──────────╯\n                  ▼\n     #+BEGIN: aggregate :table \"original\" :cols \"count() Day Color\"\n                                                 ╰──┬──╯ ╰┬╯ ╰─┬─╯\n          ╭─────────────────────────────────────────╯     │    │\n          │       ╭───────────────────────────────────────╯    │\n          │       │            ╭───────────────────────────────╯\n       ╭──┴──╮   ╭┴╮         ╭─┴─╮\n     | count() | Day       | Color |\n     |---------+-----------+-------|\n     |       1 | Monday    | Red   |\n     |       1 | Monday    | Blue  |\n     |       2 | Tuesday   | Red   |\n     |       1 | Tuesday   | Blue  |\n     |       1 | Wednesday | Red   |\n     |       2 | Wednesday | Blue  |\n     |       3 | Thursday  | Red   |\n     |       3 | Friday    | Blue  |\n     #+END\n\n   If we want to get measurements for ‘Colors’ rather than ‘Days’, we\ntype:\n\n     C-c C-c here to refresh╶╮\n                  ╭──────────╯\n                  ▼\n     #+begin: aggregate :table \"original\" :cols \"Color vmean(Level) vsum(Quantity)\"\n                                                 ╰─┬─╯ ╰────┬─────╯ ╰─────┬──────╯\n         ╭─────────────────────────────────────────╯        │             │\n         │           ╭──────────────────────────────────────╯             │\n         │           │               ╭────────────────────────────────────╯\n       ╭─┴─╮    ╭────┴─────╮   ╭─────┴──────╮\n     | Color |  vmean(Level) | vsum(Quantity) |\n     |-------+---------------+----------------|\n     | Red   | 40.2857142857 |            144 |\n     | Blue  | 15.5714285714 |             74 |\n     #+end\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Stop reading here! 80/20,  Next: Equivalent in SQL R Datamash el-tblfn Awk C++,  Prev: Examples,  Up: Top\n\n3 Stop reading here! 80/20\n**************************\n\nIf you managed to get here, you are at 80/20 (thanks Pareto!).  You\ngrasped only 20% of the OrgAggregate features, but those 20% cover 80%\nof the use cases.\n\n   To summarize the 20%:\n\n* Menu:\n\n* Name your input table::\n* Create an aggregation block::\n* Refresh the aggregation::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Name your input table,  Next: Create an aggregation block,  Up: Stop reading here! 80/20\n\n3.1 Name your input table\n=========================\n\n   • Select one of your Org table, and be ready to aggregate values from\n     it right in the same file.\n\n   • Give a name to your table with a special line just above it.\n\n     name here╶───╮\n             ╭────╯\n             ▼\n     #+name: original\n     | Day       | Color | Level | Quantity |\n     |-----------+-------+-------+----------|\n     | Monday    | Red   |    30 |       11 |\n     | Monday    | Blue  |    25 |        3 |\n     …\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Create an aggregation block,  Next: Refresh the aggregation,  Prev: Name your input table,  Up: Stop reading here! 80/20\n\n3.2 Create an aggregation block\n===============================\n\n     #+begin: aggregate\n     #+end:\n\n   • *Input:* specify a ‘:table’ parameter.\n   • *Output:* specify the desired output columns with the ‘:cols’\n     parameter.\n\n     output╶───────────────────────────────────────────╮\n     input╶───────────────────────╮                    │\n                                  │                    │\n                               ╭──┴───╮       ╭────────┴─────────╮\n     #+begin: aggregate :table original :cols \"Day vsum(Quantity)\"\n     #+end:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Refresh the aggregation,  Prev: Create an aggregation block,  Up: Stop reading here! 80/20\n\n3.3 Refresh the aggregation\n===========================\n\n   • Type ‘C-c C-c’ on the ‘#+begin:’ line now and whenever you want to\n     refresh the aggregation.\n\n     C-c C-c here╶─╮\n                   │\n                   ▼\n     #+begin: aggregate :table original :cols \"Day vsum(Quantity)\"\n     | Day       | vsum(Quantity) |\n     |-----------+----------------|\n     | Monday    |             14 |\n     | Tuesday   |             45 |\n     …\n     #+end:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Equivalent in SQL R Datamash el-tblfn Awk C++,  Next: Wizards,  Prev: Stop reading here! 80/20,  Up: Top\n\n4 Equivalent in SQL, R, Datamash, el-tblfn, Awk, C++\n****************************************************\n\nAggregation is a widely used method to get insights in tabular data.\nUse whatever environment best suits your needs.\n\n   OrgAggregate is great when you want to output and Org Mode table.\nAlso, OrgAggregate has no dependency other than Emacs, not even other\nLisp packages.\n\n* Menu:\n\n* SQL equivalent::\n* R equivalent::\n* Datamash equivalent::\n* el-tblfn::\n* Awk equivalent::\n* C++ equivalent::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: SQL equivalent,  Next: R equivalent,  Up: Equivalent in SQL R Datamash el-tblfn Awk C++\n\n4.1 SQL equivalent\n==================\n\nIf you are familiar with SQL, you would get a similar result with the\n‘GROUP BY’ statement:\n\n     select Day, mean(Level), sum(Quantity)\n     from original\n     group by Day;\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: R equivalent,  Next: Datamash equivalent,  Prev: SQL equivalent,  Up: Equivalent in SQL R Datamash el-tblfn Awk C++\n\n4.2 R equivalent\n================\n\nIf you are familiar with the R statistical language, you would get a\nsimilar result with ‘factor’ and ‘aggregate’ functions:\n\n     original <- the table as a data.frame\n     day_factor <- factor(original$Day)\n     aggregate (original$Level   , list(Day=day_factor), mean)\n     aggregate (original$Quantity, list(Day=day_factor), sum )\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Datamash equivalent,  Next: el-tblfn,  Prev: R equivalent,  Up: Equivalent in SQL R Datamash el-tblfn Awk C++\n\n4.3 Datamash equivalent\n=======================\n\nThe command-line Datamash software operates on CSV files and can achieve\na similar result:\n\n     datamash -H -g Day mean Level sum Quantity <original.csv\n     GroupBy(Day)  mean(Level)  sum(Quantity)\n     Monday        27.5         14\n     Tuesday       43           45\n     Wednesday     18           54\n     Thursday      43           83\n     Friday         8           22\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: el-tblfn,  Next: Awk equivalent,  Prev: Datamash equivalent,  Up: Equivalent in SQL R Datamash el-tblfn Awk C++\n\n4.4 el-tblfn\n============\n\nThe Misohena’s el-tblfn package aggregates Org Mode tables through Lisp\nscripts.  See <https://github.com/misohena/el-tblfn>.  Example:\n\n     #+begin_src elisp :var original=original :colnames no :hlines yes\n     (require 'tblfn)\n     (thread-first\n       (tblfn-aggregate original \"Quantity\" \"Total\"))\n     #+end_src\n\n   It is not clear how el-tblfn can compute the ‘mean’ (or other\naggregating functions) on the ‘Level’ column.  Would the author want to\ncomplete the example?\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Awk equivalent,  Next: C++ equivalent,  Prev: el-tblfn,  Up: Equivalent in SQL R Datamash el-tblfn Awk C++\n\n4.5 Awk equivalent\n==================\n\nAwk is a line-oriented filter which has been arround in Unix for\ndecades.  Here we use the standard Org Mode support for Awk.\n\n     #+begin_src awk :stdin original\n     NR>1 {\n          Day         =$1\n          Color       =$2\n          Level       =$3\n          Quantity    =$4\n          SumLevel[Day]    += Level\n          SumQuantity[Day] += Quantity\n          Count[Day]       ++\n     }\n     END {\n         for (d in SumQuantity) {\n             printf \"%s %s %s\\n\", d, SumLevel[d]/Count[d], SumQuantity[d]\n         }\n     }\n     #+end_src\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: C++ equivalent,  Prev: Awk equivalent,  Up: Equivalent in SQL R Datamash el-tblfn Awk C++\n\n4.6 C++ equivalent\n==================\n\nC++ has hash-maps in its standard template library.  And Org Mode\nprovides support for C++ Babel blocks.  Thus, it is quite\nstraigthforward to aggegrate in this language.\n\n   (Don’t forget to customize ‘org-src-lang-modes’ to activate C++\nsupport in Org Mode).\n\n     #+begin_src C++ :var original=original :includes '(<iostream> <unordered_map>)\n     using namespace std;\n     unordered_map<string,double> SumLevel;\n     unordered_map<string,double> SumQuantity;\n     unordered_map<string,double> Count;\n     for (auto row : original) {\n       auto Day      = row[0];\n       auto Color    = row[1];\n       auto Level    = stod(row[2]);\n       auto Quantity = stod(row[3]);\n       SumLevel[Day]    += Level;\n       SumQuantity[Day] += Quantity;\n       Count[Day]       ++;\n     }\n     for (auto it : SumQuantity)\n       cout<<it.first<<\" \"\n           <<SumLevel[it.first]/Count[it.first]<<\" \"\n           <<SumQuantity[it.first]<<\"\\n\";\n     #+end_src\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Wizards,  Next: The cols parameter,  Prev: Equivalent in SQL R Datamash el-tblfn Awk C++,  Up: Top\n\n5 Wizards\n*********\n\nThere are now 2 wizards.  Use whichever is easier for you.\n\n* Menu:\n\n* Guiding (traditional) wizard::\n* Experimental free form wizard::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Guiding (traditional) wizard,  Next: Experimental free form wizard,  Up: Wizards\n\n5.1 Guiding (traditional) wizard\n================================\n\nType ‘C-c C-x x’ to launch a wizard for creating new dynamic blocks, or\namending existing ones.  Then answer ‘aggregate’ for the type of block,\nand follow the instructions.  (There are several other _dynamic blocks_\nthat can be built this way: ‘columnview’, ‘clocktable’, ‘propview’,\n‘invoice’, ‘transpose’, and any future block).\n\n   There are two modes:\n   • basic, ‘C-c C-x x’, ‘M-x orgtbl-aggregate-insert-dblock-aggregate’\n   • expert, ‘C-u C-c C-x x’, ‘C-u M-x\n     orgtbl-aggregate-insert-dblock-aggregate’\n\n   The basic mode only queries for an input table in the current buffer\nand the output columns.  Of course, if the wizard updates an existing\nblock with exotic parameters, those parameters will be queried even in\nbasic mode.\n\n   The expert mode queries every parameter.\n\n   The wizard has been enhanced & extended.\n\n   It can update an existing block.  Put the cursor on a line beginning\nwith ‘#+begin: aggregate …’ and type ‘C-c C-x x’.  The wizard will use\nvalues picked from this line as defaults for user queries.\n\n   The wizard now takes into account the multiple forms of input tables.\nTables may lie in distant files as well as in the current buffer.  They\nmay be computed on the fly by Babel blocks as well as regular Org Mode\ntables.  They may also be pointed to by Org IDs.\n\n   Regular Org Mode table slicing is now recognized.\n\n   Pre & post processors are now handled by the wizard.\n\n   The ‘:hline’ parameter is taken into account.\n\n   Most of the wizard’s queries may be answered by just hitting RETURN.\nThis is because many parameters are optional.  And because when amending\nan existing block, the wizard suggests the old values as the default\nanswers.\n\n   When invoking the wizard, a small window opens to display help\nrelative to each field the wizard queries.  This help window is in Org\nMode for an easy reading.  The window will be closed when the wizard is\ndone or when aborting.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Experimental free form wizard,  Prev: Guiding (traditional) wizard,  Up: Wizards\n\n5.2 Experimental free form wizard\n=================================\n\nSuppose we have an aggregate block like this one:\n\n     the 2 main parameters╶────────╮────────────────────╮\n                                   │                    │\n                               ╭───┴────╮       ╭───────┴───────╮\n     #+begin: aggregate :table \"my_table\" :cols \"vsum(b) count()\" :hline \"1\"\n     #+end:\n\n   Then, hitting the ‘TAB’ key anywhere on the ‘#+begin:’ line (but not\nat the beginning) unfolds it.  The result is this one:\n\n     #+begin: aggregate                   ╭─╴the 2 main parameters\n     #+aggregate: :file                   │\n     #+aggregate: :name my_table        ◀─╯\n     #+aggregate: :orgid                  │\n     #+aggregate: :params                 │\n     #+aggregate: :slice                  │\n     #+aggregate: :precompute             │\n     #+aggregate: :cols vsum(b) count() ◀─╯\n     #+aggregate: :cond\n     #+aggregate: :hline 1\n     #+aggregate: :post\n     #+end:\n\n   This unfolded form is easier to read.  Furthermore, help and\ncompletion is available on each line, by hitting the ‘TAB’ key.\n\n   Hit ‘TAB’ again on the ‘#+begin:’ line (not at the beginning) to fold\nback the form.  Then hit ‘C-c C-c’ as usual to refresh the aggregation.\n\n   The usual behaviour of Org Mode ‘TAB’ is preserved when the ‘TAB’ key\nis pressed at the beginning of line.\n\n   All fields recognized by OrgAggregate are displayed, even when they\nare empty.  The empty fields will be ignored when folding back.  This\nallows to grasp all the parameters in a single eyesight.\n\n   All in all, this new wizard covers the same features as the\ntraditional, guiding wizard.  Except, one can fill the fields in any\norder, and just ignore the unnecessary ones.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: The cols parameter,  Next: Column names,  Prev: Wizards,  Up: Top\n\n6 The :cols parameter\n*********************\n\nThe ‘:cols’ parameter lists the columns of the resulting table.  It\ncontains in any order, grouping key columns and aggregation formulas.\nThe choosen order will be reflected in the output table.\n\n* Menu:\n\n* Names of input columns::\n* Grouping specifications incols: Grouping specifications in cols.\n* The hline column::\n* The @# column::\n* Aggregation formulas incols: Aggregation formulas in cols.\n* Correlation of two columns::\n* (Almost) any expression can be specified: [Almost) any expression can be specified.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Names of input columns,  Next: Grouping specifications in cols,  Up: The cols parameter\n\n6.1 Names of input columns\n==========================\n\nThe names of the columns in the original table may be:\n   • the names as they appear in the header of the source table,\n   • or ‘$1’, ‘$2’, ‘$3’ and so on (as in spreadsheet formulas),\n   • additionally, the special column ‘hline’ is used to group parts of\n     the source table separated by horizontal lines.\n   • the special ‘@#’ column, not in the input table, is the row number.\n\n   The ‘:cols’ parameter may be a string or a list of strings.\nExamples:\n\n     :cols \"Day vmean(Level);f3 vsum(Quantity);f2\"\n     :cols (\"Day\" \"vmean(Level);f3\" \"vsum(Quantity);f2\")\n\n   If a single string is used, it is split by spaces.  Thus, a given\nformula, including its semicolon and modifiers, must not contain any\nspace.  If spaces are required within a formula, then use the\nparenthesis list.  If a column name has spaces, quote it like this:\n\n     'yellow submarine'\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Grouping specifications in cols,  Next: The hline column,  Prev: Names of input columns,  Up: The cols parameter\n\n6.2 Grouping specifications in :cols\n====================================\n\nGrouping is done on columns of the source table acting as key columns.\nJust name the key columns.\n\n   Additionally, the ‘hline’ specification means that rows between two\nhorizontal lines should be grouped.\n\n   Key columns and ‘hline’ are used to group rows of the source table\nwith unique combinations of those columns.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: The hline column,  Next: The @# column,  Prev: Grouping specifications in cols,  Up: The cols parameter\n\n6.3 The hline column\n====================\n\nhline = \"horizontal line\"\n\n   The special column named ‘hline’ gives the block number in the source\ntable.  A block is the rows separated by horizontal lines in the input\ntable.  The first block is numbered ‘0’.  It is a virtual column in the\nsense that it is not a column in the input table.  Other than that, it\nis usable as any real column.\n\n   Here is a source table containing 3 blocks separated by horizontal\nlines:\n\n     #+name: originalhl\n     | Color | Level | Quantity |\n     |-------+-------+----------| ╶╮\n     | Red   |    30 |       11 |  │\n     | Blue  |    25 |        3 |  ├─first block, n° 0\n     | Red   |    51 |       12 |  │\n     | Red   |    45 |       15 |  │\n     | Blue  |    33 |       18 | ╶╯\n     |-------+-------+----------| ╶╮\n     | Red   |    27 |       23 |  │\n     | Blue  |    12 |       16 |  ├─second block, n° 1\n     | Blue  |    15 |       15 |  │\n     | Red   |    39 |       24 |  │\n     | Red   |    41 |       29 | ╶╯\n     |-------+-------+----------| ╶╮\n     | Red   |    49 |       30 |  │\n     | Blue  |     7 |        5 |  ├─third block, n° 2\n     | Blue  |     6 |        8 |  │\n     | Blue  |    11 |        9 | ╶╯\n\n   And here is the aggregation by those 3 blocks:\n\n     #+BEGIN: aggregate :table originalhl :cols \"hline vmean(Level) vsum(Quantity)\"\n     | hline | vmean(Level) | vsum(Quantity) |\n     |-------+--------------+----------------|\n     |     0 |         36.8 |             59 | ◀─╴first block , n° 0\n     |     1 |         26.8 |            107 | ◀─╴second block, n° 1\n     |     2 |        18.25 |             52 | ◀─╴third block , n° 2\n     #+END:\n\n   If we want additional details with the ‘Color’ column, we just name\nit:\n\n     #+begin: aggregate :table originalhl :cols \"hline Color vmean(Level) vsum(Quantity)\"\n     | hline | Color |  vmean(Level) | vsum(Quantity) |\n     |-------+-------+---------------+----------------|\n     |     0 | Red   |            42 |             38 | ╶┬──╴first block , n° 0\n     |     0 | Blue  |            29 |             21 | ╶╯\n     |     1 | Red   | 35.6666666667 |             76 | ╶┬──╴second block, n° 1\n     |     1 | Blue  |          13.5 |             31 | ╶╯\n     |     2 | Red   |            49 |             30 | ╶┬──╴third block , n° 2\n     |     2 | Blue  |             8 |             22 | ╶╯\n     #+end:\n\n   There is an ugly value, ‘35.6666666667’, in the middle of the table.\nSee later how to format it (*note Org Mode compatible formatters::).\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: The @# column,  Next: Aggregation formulas in cols,  Prev: The hline column,  Up: The cols parameter\n\n6.4 The @# column\n=================\n\nThe special column named ‘@#’ gives the row number in the source table.\nThe ‘@#’ name is the same as in the Org table spreadsheet formulas.  It\nis a virtual column in the sense that it does not appear in the input\ntable.  Other than that, it is usable as any real column.\n\n   Example: we compute the average of the row number for colors ‘Red’ &\n‘Blue’.  This gives a sense of whether each colors appears rather at the\nbeginning or the end of the table.\n\n     #+begin: aggregate :table \"original\" :cols \"Color vmean(@#)\"\n     | Color | vmean(@#) |\n     |-------+-----------|\n     | Red   | 6.2857143 |\n     | Blue  | 8.7142857 |\n     #+end:\n\n   Example: we recover the input table in reverse order.  We use ‘@#’ as\na key column sorted in decreasing order with ‘;^N’.  Then we make it\ninvisible with ‘;<>’ so that it does not appear in the output.\n\n                             invisible╶─────────────────────────────────────────╮\n                             sorted numerically decreasing╶───────────────────╮ │\n                             row numbers of input table╶──────────────────╮   │ │\n                                                                          │   │ │\n                                                                          ▼   ▼ ▼\n     #+begin: aggregate :table \"original\" :cols \"Day Color Level Quantity @#;^N;<>\"\n     | Day       | Color | Level | Quantity |\n     |-----------+-------+-------+----------|\n     | Friday    | Blue  |    11 |        9 |\n     | Friday    | Blue  |     6 |        8 |\n     | Friday    | Blue  |     7 |        5 |\n     | Thursday  | Red   |    49 |       30 |\n     | Thursday  | Red   |    41 |       29 |\n     | Thursday  | Red   |    39 |       24 |\n     | Wednesday | Blue  |    15 |       15 |\n     | Wednesday | Blue  |    12 |       16 |\n     | Wednesday | Red   |    27 |       23 |\n     | Tuesday   | Blue  |    33 |       18 |\n     | Tuesday   | Red   |    45 |       15 |\n     | Tuesday   | Red   |    51 |       12 |\n     | Monday    | Blue  |    25 |        3 |\n     | Monday    | Red   |    30 |       11 |\n     #+end:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Aggregation formulas in cols,  Next: Correlation of two columns,  Prev: The @# column,  Up: The cols parameter\n\n6.5 Aggregation formulas in :cols\n=================================\n\nAggregation formulas are applied for each of the groupings, on the\nspecified columns.\n\n   We saw examples with ‘sum’, ‘mean’, ‘count’ aggregations.  There are\nmany other aggregations.  They are based on functions provided by Calc\n(Calc is the powerful Emacs calculator):\n\n   • ‘count()’ or ‘vcount()’\n        • in Calc: ‘`u #' (`calc-vector-count') [`vcount'])’\n        • gives the number of elements in the group being aggregated;\n          this function may or may not take a column parameter; with a\n          parameter, empty cells are not counted (except with the ‘E’\n          modifier)..\n\n   • ‘sum(X)’ or ‘vsum(X)’\n        • in Calc: ‘`u +' (`calc-vector-sum') [`vsum']’\n        • computes the sum of elements being aggregated\n\n   • ‘cnorm(X)’\n        • in Calc: ‘`v N' (calc-cnorm') [`cnorm']’\n        • like ‘vsum(X)’, compute the sum of values, but first replacing\n          negative values by their opposite\n\n   • ‘max(X)’ or ‘vmax(X)’\n        • in Calc: ‘`u X' (`calc-vector-max') [`vmax']’\n        • gives the largest of the elements being aggregated\n\n   • ‘min(X)’ or ‘vmin(X)’\n        • in Calc: ‘`u N' (`calc-vector-min') [`vmin']’\n        • gives the smallest of the elements being aggregated\n\n   • ‘span(X)’ or ‘vspan(X)’\n        • in Calc: ‘`v :' (`calc-set-span') [`vspan']’\n        • summarizes values to be aggregated into an interval\n          ‘[MIN..MAX]’ where ‘MIN’ and ‘MAX’ are the minimal and maximal\n          values to be aggregated\n\n   • ‘rnorm(X)’\n        • in Calc: ‘`v n' (`calc-rnorm) [`rnorm']’\n        • like ‘vmax(X)’, gives the maximum of values, but first\n          replacing negative values by their opposite\n\n   • ‘mean(X)’ or ‘vmean(X)’\n        • in Calc: ‘`u M' (`calc-vector-mean') [`vmean']’\n        • computes the average (arithmetic mean) of elements being\n          aggregated\n\n   • ‘meane(X)’ or ‘vmeane(X)’\n        • in Calc: ‘`I u M' (`calc-vector-mean-error') [`vmeane']’\n        • computes the average (as mean) along with the estimated error\n          of elements being aggregated\n\n   • ‘median(X)’ or ‘vmedian(X)’\n        • in Calc: ‘`H u M' (`calc-vector-median') [`vmedian']’\n        • computes the median of elements being aggregated, by taking\n          the middle element after sorting them\n\n   • ‘hmean(X)’ or ‘vhmean(X)’\n        • in Calc: ‘`H I u M' (`calc-vector-harmonic-mean') [`vhmean']’\n        • computes the harmonic mean of elements being aggregated\n\n   • ‘gmean(X)’ or ‘vgmean(X)’\n        • in Calc: ‘`u G' (`calc-vector-geometric-mean') [`vgmean']’\n        • computes the geometric mean of elements being aggregated\n\n   • ‘sdev(X)’ or ‘vsdev(X)’\n        • in Calc: ‘`u S' (`calc-vector-sdev') [`vsdev']’\n        • computes the standard deviation of elements being aggregated\n\n   • ‘psdev(X)’ or ‘vpsdev(X)’\n        • in Calc: ‘`I u S' (`calc-vector-pop-sdev') [`vpsdev']’\n        • computes the population standard deviation (divide by N\n          instead of N-1)\n\n   • ‘var(X)’ or ‘vvar(X)’\n        • in Calc: ‘`H u S' (`calc-vector-variance') [`vvar']’\n        • computes the variance of elements being aggregated\n\n   • ‘pvar(X)’ or ‘vpvar(X)’\n        • in Calc: ‘`H u S' (`calc-vector-variance') [`vpvar']’\n        • computes the population variance of elements being aggregated\n\n   • ‘pcov(X,Y)’ or ‘vpcov(X,Y)’\n        • in Calc: ‘`I u C' (`calc-vector-pop-covariance') [`vpcov']’\n        • computes the population covariance of elements being\n          aggregated from two columns (divides by N)\n\n   • ‘cov(X,Y)’ or ‘vcov(X,Y)’\n        • in Calc: ‘`u C' (`calc-vector-covariance') [`vcov']’\n        • computes the sample covariance of elements being aggregated\n          from two columns (divides by N-1)\n\n   • ‘corr(X,Y)’ or ‘vcorr(X,Y)’\n        • in Calc: ‘`H u C' (`calc-vector-correlation') [`vcorr']’\n        • computes the linear correlation coefficient of elements being\n          aggregated in two columns\n\n   • ‘prod(X)’ or ‘vprod(X)’\n        • in Calc: ‘`u *' (`calc-vector-product') [`vprod']’\n        • computes the product of elements being aggregated\n\n   • ‘vlist(X)’ or ‘list(X)’\n        • gives the list of ‘X’ being aggregated, verbatim, without\n          aggregation.\n\n   • ‘(X)’ or ‘X’ in a formula\n        • returns the list of ‘X’ being aggregated, without aggregation,\n          passed through Calc interpretation.\n\n   • ‘sort(X)’\n        • in Calc: ‘`v S' (`calc-sort') [`sort']’\n        • sorts elements to be aggregated in ascending order; only works\n          on numerical values\n\n   • ‘rsort(X)’\n        • in Calc: ‘`I v S' (`calc-sort') [`sort']’\n        • sorts elements to be aggregated in descending order; only\n          works on numerical values\n\n   • ‘rev(X)’\n        • in Calc: ‘`' (`calc-reverse-vector') [`rev']’\n        • returns the list of values to be aggregated in reverse order\n\n   • ‘subvec(X,from)’, ‘subvec(X,from,to)’\n        • in Calc: ‘`v s' (`calcFunc-subvec') [`subvec']’\n        • extracts a sub-list from ‘X’ starting at ‘from’ and ending at\n          ‘to’ excluded (or up to the end if ‘to’ is not given).  The\n          first value is numbered ‘1’.  So for instance ‘subvec(X,1,3)’\n          extracts the first two values\n\n   • ‘vmask(M,X)’\n        • in Calc: ‘`v m' (`calcFunc-vmask') [`vmask']’\n        • extracts a sub-list from ‘X’, keeping only values for which\n          corresponding values in ‘M’ (the mask) are not zero\n\n   • ‘head(X)’\n        • in Calc: ‘`v h' (`calc-head') [`head']’\n        • returns the first value to be aggregated\n\n   • ‘rtail(X)’\n        • in Calc: ‘`H I v h' (`calc-head') [`rtail']’\n        • returns the last value to be aggregated\n\n   • ‘find(X,val)’\n        • in Calc: ‘`v f' (`calc-vector-find') [`find']’\n        • returns the index of ‘val’ in the list of values to be\n          aggregated, or ‘0’ if ‘val’ is not found.  Index starts from\n          ‘1’\n\n   • ‘rdup(X)’\n        • in Calc: ‘`v +' (`calc-remove-duplicates') [`rdup']’\n        • remove duplicates from ‘X’ and returns remaining values sorted\n          in ascending order\n\n   • ‘grade(X)’\n        • in Calc: ‘`v G' (`calc-grade') [`grade']’\n        • returns a list of index of values to be aggregated: the index\n          of the lowest value, then the second lowest value, and so on\n          up to the index of the highest value.  Indexes start from ‘1’\n\n   • ‘rgrade(X)’\n        • in Calc: ‘`I v G' (`calc-grade') [`rgrade']’\n        • Like ‘grade’ in reverse order\n\n   The aggregation functions may be written with or without a leading\n‘v’.  ‘sum’ and ‘vsum’ are equivalent.  The ‘v’ form should be\npreferred, as it is the one used in the Org table spreadsheet, and in\nCalc.  The non-v names may be dropped in the future.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Correlation of two columns,  Next: [Almost) any expression can be specified,  Prev: Aggregation formulas in cols,  Up: The cols parameter\n\n6.6 Correlation of two columns\n==============================\n\nSome aggregations work on two columns (rather than one column for\n‘vsum()’, ‘vmean()’).  Those aggregations are ‘vcov(,)’, ‘vpcov(,)’,\n‘vcorr(,)’.\n   • ‘vcorr(,)’ computes the linear correlation between two columns.\n   • ‘vcov(,)’ and ‘vpcov(,)’ compute the covariance of two columns.\n\n   Example.  We create a table where column ‘y’ is a noisy version of\ncolumn ‘x’:\n\n     #+tblname: noisydata\n     | bin   |  x |       y |\n     |-------+----+---------|\n     | small |  1 |  10.454 |\n     | small |  2 |  21.856 |\n     | small |  3 |  30.678 |\n     | small |  4 |  41.392 |\n     | small |  5 |  51.554 |\n     | large |  6 |  61.824 |\n     | large |  7 |  71.538 |\n     | large |  8 |  80.476 |\n     | large |  9 |  90.066 |\n     | large | 10 | 101.070 |\n     | large | 11 | 111.748 |\n     | large | 12 | 121.084 |\n     #+tblfm: $3=$2*10+random(1000)/500;%.3f\n\n     #+BEGIN: aggregate :table noisydata :cols \"bin vcorr(x,y) vcov(x,y) vpcov(x,y)\"\n     | bin   |     vcorr(x,y) |     vcov(x,y) |    vpcov(x,y) |\n     |-------+----------------+---------------+---------------|\n     | small | 0.999459736649 |        25.434 |       20.3472 |\n     | large | 0.999542438688 | 46.4656666667 | 39.8277142857 |\n     #+END\n\n   We see that the correlation between ‘x’ and ‘y’ is very close to ‘1’,\nmeaning that both columns are correlated.  Indeed they are, as the ‘y’\nis computed from ‘x’ with the formula\n     y = 10*x + noise_between_0_and_2\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: [Almost) any expression can be specified,  Prev: Correlation of two columns,  Up: The cols parameter\n\n6.7 (Almost) any expression can be specified\n============================================\n\nVirtually any Calc formula can be specified as an aggregation formula.\n\n   Single column name (as they appear in the header of the source table,\nor in the form of ‘$1’, ‘$2’, ..., or the virtual columns ‘hline’ and\n‘@#’) are key columns.  Everything else is given to Calc, to be computed\nas an aggregation.\n\n   For instance:\n     (3)                        ;; a constant\n     vmean(2*X+1)               ;; aggregate an expression\n     exp(vmean(map(log,N)))     ;; the exponential average\n     vsum((X-vmean(X))^2)       ;; X-vmean(X) centers the sample on zero\n\n   Arguably, the first expression is useless, but legal.  The\naggregation can be applied to a computed list of values.  The result of\nan aggregation can be further processed in a formula.  An aggregation\ncan even be applied to an expression containing another aggregation.\n\n   In an expression, if a variable has the name of a column, then it is\nreplaced by a Calc vector containing values from this column.\n\n   The special expression ‘(C)’ (a column name within parenthesis)\nyields a list of values to be aggregated from this column, except they\nare not aggregated.  Note that parenthesis are required, otherwise, ‘C’\nwould act as a key grouping column.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Column names,  Next: Formatters,  Prev: The cols parameter,  Up: Top\n\n7 Column names\n**************\n\n* Menu:\n\n* Input table with or without a header::\n* Column names of the input table::\n* Multiple lines header::\n* Custom column names::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Input table with or without a header,  Next: Column names of the input table,  Up: Column names\n\n7.1 Input table with or without a header\n========================================\n\nThe header of a table gives names to its columns.  It is separated from\ndata with an horizontal line.\n\n     column name is \"quantity\" or \"$2\"╶╮\n     column name is \"day\" or \"$1\"╶╮    │\n        ╭─────────────────────────╯    │\n        │              ╭───────────────╯\n        ▼              ▼\n     | day       | quantity |\n     |-----------+----------|\n     | monday    |     12.3 |\n     | monday    |      5.9 |\n     | thursday  |     41.1 |\n     | wednesday |     16.8 |\n\n   In this example, the input columns may be referred to as ‘day’ and\n‘quantity’.\n\n   Tables without a header are handled by OrgAggregate with _\"dollar\nnames\"_.  Example of a table without a header:\n\n     column name is \"$2\"╶──╮\n     column name is \"$1\"╶╮ │\n         ╭───────────────╯ │\n         │               ╭─╯\n         ▼               ▼\n     | monday    |     12.3 |\n     | monday    |      5.9 |\n     | thursday  |     41.1 |\n     | wednesday |     16.8 |\n\n   Then columns may be refereed to as ‘$1’ and ‘$2’.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Column names of the input table,  Next: Multiple lines header,  Prev: Input table with or without a header,  Up: Column names\n\n7.2 Column names of the input table\n===================================\n\nColumn names are not necessarily alphanumeric words.  They may contain\nany characters, including spaces, quotes, +, -, whatever.  They must not\nextend on several lines thought.\n\n   Those names need to be protected with quotes (single or double\nquotes) within formulas.\n\n   Examples:\n   • ‘:cols’ \"‘mean('estimated value')’\"\n   • ‘:cond (equal \"true color\" \"Red\")’\n\n   Quoting is not required for\n   • ASCII letters\n   • numbers\n   • underscore _, dollar $, dot .\n   • accented letters like à é\n   • Greek letters like α, Ω\n   • northern letters like ø\n   • Russian letters like й\n   • Esperanto letters like ŭ\n   • Japanese ideograms like 量\n\n   Note that in ‘:cond’ Lisp expression, only double quotes work.  This\nis because single quotes in Lisp have a very special meaning.\n\n   ‘Ubuntu Mono’ font can be used for displaying aligned Japanese\ncharacters, although not perfectly.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Multiple lines header,  Next: Custom column names,  Prev: Column names of the input table,  Up: Column names\n\n7.3 Multiple lines header\n=========================\n\nThe header of the source table may be more than one row tall.  Only the\nfirst header row is used to match column names between the source table\nand the ‘:cols’ specifications.\n\n   Best effort is made to propagate additional header rows to the\naggregated table.  This happens when the aggregated column refers to a\nsingle source column, either as a key column or a formula involving a\nsingle column.\n\n     #+name: tall-header\n\n                    ╭──────────────────────────────────────────────╮\n                ╭───┴──╮                                           │\n     | color  | quantity |  level | ╶╮                             │\n     | <l>    |     <r7> |    <3> |  ├─╴header is 3 rows tall      │\n     | kolor  |     kiom | nivelo | ╶╯                             │\n     |--------+----------+--------|                    ╭───────────╯───────────────╮\n     | yellow |       72 |      3 |                    │ only the first header row │\n     | green  |       55 |      5 |                    │ is used in formulas       │\n     | <c>    |          |        |                    ╰───────────╭───────────────╯\n     | orange |       80 |      2 |                                │\n     | yellow |       13 |      1 |                                │\n                                                               ╭───┴──╮\n     #+BEGIN: aggregate :table \"tall-header\" :cols \"color vsum(quantity);'sum' count();'nb' vsum(quantity)/vmean(level);'leveled'\"\n     | color  |  sum | nb | leveled | ╶╮\n     | <l>    | <r7> |    |         |  ├──╮\n     | kolor  | kiom |    |         | ╶╯  │  ╭────────────────────────╮\n     |--------+------+----+---------|     │  │ best attempt to recover│\n     | yellow |   85 |  2 |    42.5 |     ╰──┤ the three header rows  │\n     | green  |   55 |  1 |      11 |        │ in the output          │\n     | orange |   80 |  1 |      40 |        ╰────────────────────────╯\n     #+END:\n\n   Note that the last aggregated column has just ‘leveled’ in its\nheader.  This is because this column refers to more than one source\ncolumns, namely ‘quantity’ and ‘level’.\n\n   Note that in this example, there are formatting cookies:\n     <> <l> <c> <r> <7> <l7> <c7> <r7>\n\n   Data rows containing at least one cookie are ignored.  They are not\nignored in the header.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Custom column names,  Prev: Multiple lines header,  Up: Column names\n\n7.4 Custom column names\n=======================\n\nIn this example, output column have names which are difficult to handle:\n\n     #+BEGIN: aggregate :table original :cols \"Day vmean(Level*2) vsum(Quantity^2)\"\n                                                   ╰─────┬──────╯ ╰──────┬───────╯\n                         ╭───────────────────────────────╯               │\n                         │                 ╭─────────────────────────────╯\n                   ╭─────┴──────╮   ╭──────┴───────╮\n     | Day       | vmean(Level*2) | vsum(Quantity^2) |\n     |-----------+----------------+------------------|\n     | Monday    |             55 |              130 |\n     | Tuesday   |             86 |              693 |\n     | Wednesday |             36 |             1010 |\n     | Thursday  |             86 |             2317 |\n     | Friday    |             16 |              170 |\n     #+END\n\n   We can give them custom names with the ‘;'custom name'’ decoration:\n\n     #+BEGIN: aggregate :table original :cols \"Day vmean(Level*2);'mean2' vsum(Quantity^2);'sum_squares'\"\n                                                                  ╰──┬──╯                  ╰─────┬─────╯\n                     ╭───────────────────────────────────────────────╯                           │\n                     │         ╭─────────────────────────────────────────────────────────────────╯\n                   ╭─┴─╮   ╭───┴─────╮\n     | Day       | mean2 | sum_squares |\n     |-----------+-------+-------------|\n     | Monday    |    55 |         130 |\n     | Tuesday   |    86 |         693 |\n     | Wednesday |    36 |        1010 |\n     | Thursday  |    86 |        2317 |\n     | Friday    |    16 |         170 |\n     #+END\n\n   Decorators are optional.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Formatters,  Next: Sorting,  Prev: Column names,  Up: Top\n\n8 Formatters\n************\n\n* Menu:\n\n* Org Mode compatible formatters::\n* Debugging formatters::\n* Discarding an output column::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Org Mode compatible formatters,  Next: Debugging formatters,  Up: Formatters\n\n8.1 Org Mode compatible formatters\n==================================\n\nAn expression may optionally be followed by modifiers and formatters,\nafter a semicolon.  Examples:\n\n     vsum(X);p20    ;; increase Calc internal precision to 20 digits\n     vsum(X);f3     ;; output the result with 3 digits after the decimal dot\n     vsum(X);%.3f   ;; output the result with 3 digits after the decimal dot\n\n   The modifiers and formatters are fully compatible with those of the\nOrg Mode spreadsheet.\n\n   • ‘p12’ change the precision to 12 decimal digits.\n   • ‘n7’ output as floating point number with 7 decimal digits.\n   • ‘f4’ output number with 4 decimal places after dot.\n   • ‘s5’ output number in \"scientific\" mode (with exponent of 10) with\n     5 decimal digits.\n   • ‘e6’ output number in \"engineering\" mode (with exponent of 10\n     multiple of 3) with 6 decimal digits.\n   • ‘t’ output duration in decimal hours; input is supposed to be\n     either a duration like ‘\"2:37\"’ meaning 2 hours and 37 minutes, or\n     a number of seconds like ‘\"1234’\" which is approximately ‘0.34’\n     hours.  The output is controlled by the\n     ‘org-table-duration-custom-format’ variable.\n   • ‘T’ output duration in an hours-minutes-seconds format like\n     ‘\"01:20:34\"’ meaning 1 hour, 20 minutes, and 34 seconds.\n   • ‘U’ like ‘t’, but disregard the ‘org-table-duration-custom-format’\n     variable and use ‘hh:mm’ in place.\n   • ‘N’ output number: remove any non-numeric output.\n   • ‘E’ keep empty input cells.  The result is often ‘nan’.  Without\n     ‘E’, empty input cells are ignored as if they did not exist.\n   • ‘D’ angles are in degrees.\n   • ‘R’ angles are in radians.\n   • ‘F’ output is presented as a fraction of integers if it actually\n     is.  The format is the Calc one, for example ‘\"2:3\"’ means ‘2/3’.\n   • ‘S’ symbolic mode.  When an input cell is, for instance ‘sqrt(2)’,\n     it it kept as-is rather than being replaced by ‘1.41421’.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Debugging formatters,  Next: Discarding an output column,  Prev: Org Mode compatible formatters,  Up: Formatters\n\n8.2 Debugging formatters\n========================\n\nAdditionally, a few formatters are dedicated to debugging:\n\n   • ‘c’ output the Calc expression before substitution by actual input\n     cells values.\n   • ‘q’ output the Lisp expression before substitution by actual input\n     cells values.\n   • ‘C’ output the Calc expression before it gets simplified and\n     folded.\n   • ‘Q’ output the Lisp expression before it gets simplified and\n     folded.\n\n   See *note Debugging:: for a detailed explanation.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Discarding an output column,  Prev: Debugging formatters,  Up: Formatters\n\n8.3 Discarding an output column\n===============================\n\nWhy would anyone specify a column just to discard it in the output?  For\nits side effects.  For sorting the output table or for adding hlines to\nit.\n\n   To discard a column, add a ‘;<>’ modifier to the column description.\nThis syntax is reminiscent of the ‘<n>’ cookies in Org Mode tables,\nwhich instructs to shorten a column width to only ‘n’ characters.\n\n   In this example, input hlines create a ‘hline’ column which is used\nto add hlines to the output.  Then this ‘hline’ column is discarded with\n‘<>’.\n\n     invisible╶────────────────────────────────────────────╮\n     sorted numerically increasing╶──────────────────────╮ │\n                                                         │ │\n                                                         ▼ ▼\n     #+BEGIN: aggregate :table \"withhline\" :cols \"hline;^n;<> cölØr vsum(vâluε)\" :hline 1\n     | cölØr  | vsum(vâluε) |\n     |--------+-------------|\n     | Red    |         7.4 |\n     | Yellow |         9.1 |\n     |--------+-------------|\n     | Blue   |        15.7 |\n     | Yellow |         5.4 |\n     |--------+-------------|\n     | Blue   |         4.9 |\n     | Red    |         3.9 |\n     | Yellow |          9. |\n     |--------+-------------|\n     | Red    |         1.1 |\n     | Yellow |         3.4 |\n     #+END:\n\n   Here is an example where rows are sorted on the ‘cölØr’ column, but\nwithout displaying this column:\n\n     invisible╶────────────────────────────────────────────╮\n     sorted alphabetically╶──────────────────────────────╮ │\n                                                         │ │\n                                                         ▼ ▼\n     #+BEGIN: aggregate :table \"withhline\" :cols \"cölØr;^a;<> vâluε;^n\" :hline 1\n     | vâluε |                                                       ▲\n     |-------|                                                       │\n     |   4.9 |           within the same cölØr bucket,               │\n     |   7.0 |           sort vâluε numerically╶─────────────────────╯\n     |   8.7 |\n     |-------|\n     |   1.1 |\n     |   1.3 |\n     |   2.6 |\n     |   3.5 |\n     |   3.9 |\n     |-------|\n     |   2.4 |\n     |   3.4 |\n     |   5.4 |\n     |   6.6 |\n     |   9.1 |\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Sorting,  Next: hlines in the output table,  Prev: Formatters,  Up: Top\n\n9 Sorting\n*********\n\n* Menu:\n\n* Example with one sorting column::\n* Several sorting columns::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Example with one sorting column,  Next: Several sorting columns,  Up: Sorting\n\n9.1 Example with one sorting column\n===================================\n\nIn this example, the output table is sorted numerically on its second\ncolumn (look at the ‘^n’ specification):\n\n     #+begin: aggregate :table \"original\" :cols \"Day vsum(Quantity);^n\"\n     | Day       | vsum(Quantity) |\n     |-----------+----------------|\n     | Monday    |             14 |\n     | Friday    |             22 |\n     | Tuesday   |             45 |\n     | Wednesday |             54 |\n     | Thursday  |             83 |\n     #+end:\n\n   By default, no sorting is done.  The output rows follows the ordering\nof the input rows.\n\n   Any column specification in the ‘:cols’ parameter may be followed by\na semicolon and caret characters, and an ordering.\n\n   The specification for the ordering are the same as in Org Mode:\n   • ‘a’: ascending alphabetical sort\n   • ‘A’: descending alphabetical sort\n   • ‘n’: ascending numerical sort\n   • ‘N’: descending numerical sort\n   • ‘t’: ascending date, time, or duration sort\n   • ‘T’: descending date, time, or duration sort\n   • ‘f’ & ‘F’ specifications are not (yet) implemented\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Several sorting columns,  Prev: Example with one sorting column,  Up: Sorting\n\n9.2 Several sorting columns\n===========================\n\nThe rows of the resulting table may be sorted on any combination of its\ncolumns.\n\n   Several columns may get a sorting specification.  The major column is\nused for sorting.  Only when two rows are equal regarding the major\ncolumn, the second major column is compared.  And if the two rows are\nstill equal on this second column, the third is used, and so on.\n\n   The first sorted column in the ‘:cols’ parameter is the major one.\nTo declare another one as the major, follow it with a number, for\ninstance ‘1’.  Columns without a number are minor ones.\n\n   Example:\n     :cols \"AAA;^a BBB;^N2 CCC DDD;^t1\"\n     ╭────╮      ▲╷     ▲▲          ▲▲ ╭──────────────╮\n     │sort╰──────╯╰─╮   ││          │╰─╯first priority│\n     │alphabetically│   ││          ╰──╮sort by date  │\n     │third priority│   ││             ╰──────────────╯\n     ╰──────────────╯   ││  ╭────────────────╮\n                        │╰──╯second priority │\n                        ╰───╮sort numerically│\n                            │decreasing      │\n                            ╰────────────────╯\n\n   • Column ‘DDD’ is sorted in ascending dates or times (‘t’\n     specification).  It is the major sorting column (because of its ‘1’\n     numbering).\n   • Column ‘BBB’ sorts rows which compare equal on column ‘DDD’\n     (because of its ‘2’ numbering).  This column is assumed to contain\n     numerical values, and it is sorted in descending order (‘N’\n     specification).\n   • Column ‘AAA’ is used to sort rows which compare equal regarding\n     ‘DDD’ and ‘BBB’.  It is sorted in ascending alphabetical order (‘a’\n     specification).\n\n   Both a format and a sorting instruction may be given.  Example:\n     :cols \"EXPR:f3:^n\"\n\n   The ‘EXPR’ column is\n   • formatted with 3 digits after dot (‘f3’)\n   • sorted numerically in ascending order (‘^n’).\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: hlines in the output table,  Next: Cells processing,  Prev: Sorting,  Up: Top\n\n10 hlines in the output table\n*****************************\n\nThe ‘:hline N’ parameter controls horizontal lines in the output table.\nIt may or may not be related to horizontal lines in the input.\n\n* Menu:\n\n* Output hlines depends on sorting columns::\n* Example with hline 2::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Output hlines depends on sorting columns,  Next: Example with hline 2,  Up: hlines in the output table\n\n10.1 Output hlines depends on sorting columns\n=============================================\n\nExample of an input table:\n\n     #+name: withouthline\n     | cölØr  | vâluε | ra;han |\n     |--------+-------+--------|\n     | Red    |   1.3 |     41 |\n     | Red    |   3.5 |     35 |\n     | Yellow |   9.1 |     95 |\n     | Red    |   2.6 |     84 |\n     | Blue   |   8.7 |     52 |\n     | Blue   |   7.0 |     29 |\n     | Yellow |   5.4 |     17 |\n     | Blue   |   4.9 |     64 |\n     | Red    |   3.9 |     51 |\n     | Yellow |   2.4 |     55 |\n     | Yellow |   6.6 |     34 |\n     | Red    |   1.1 |     58 |\n     | Yellow |   3.4 |     51 |\n\n   Horizontal lines appear on the sorted column, which in this example\nis the ‘cölØr’ column.\n\n   We require output hlines with ‘:hline 1’.  The ‘1’ value here says\nthat only one sorted column should be considered when drawing output\nhorizontal lines.  A value of ‘2’ would mean to consider two sorted\ncolumns.\n\n   Horizontal lines will separate blocks of identical ‘cölØr’ rows:\n\n     #+BEGIN: aggregate :table \"withouthline\" :cols \"cölØr;^a vâluε 'ra;han'\" :hline 1\n     | cölØr  | vâluε | 'ra;han' |                   ╰─┬─╯  ▲                  ╰─┬─╯\n     |--------+-------+----------|                     │    │                    │\n     | Blue   |   8.7 |       52 |╶╮                   │    │╭──────╮  ╭─────────┴─────╮\n     | Blue   |   7.0 |       29 | ├─╴Blue bucket      │    ╰╯sort  │  │ separate      │\n     | Blue   |   4.9 |       64 |╶╯                   ╰─────╮cölØr │  │ cölØr buckets │\n     |--------+-------+----------| ◀────────────────╮        ╰──────╯  │ with hlines   │\n     | Red    |   1.3 |       41 |╶╮                │                  ╰──┬┬───────────╯\n     | Red    |   3.5 |       35 | │                ╰─────────────────────╯│\n     | Red    |   2.6 |       84 | ├─╴Red bucket                           │\n     | Red    |   3.9 |       51 | │                                       │\n     | Red    |   1.1 |       58 |╶╯                                       │\n     |--------+-------+----------| ◀───────────────────────────────────────╯\n     | Yellow |   9.1 |       95 |╶╮\n     | Yellow |   5.4 |       17 | │\n     | Yellow |   2.4 |       55 | ├─╴Yellow bucket\n     | Yellow |   6.6 |       34 | │\n     | Yellow |   3.4 |       51 |╶╯\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Example with hline 2,  Prev: Output hlines depends on sorting columns,  Up: hlines in the output table\n\n10.2 Example with hline 2\n=========================\n\nIn the following example, we specify ‘:hline 2’.\n\n   First, the input table now have horizontal lines.  We want to\npropagate them to the output.\n\n     #+name: withhline\n     | cölØr  | vâluε | ra;han |\n     |--------+-------+--------|\n     | Red    |   1.3 |     41 |\n     | Red    |   3.5 |     35 |\n     | Yellow |   9.1 |     95 |\n     | Red    |   2.6 |     84 |\n     |--------+-------+--------|\n     | Blue   |   8.7 |     52 |\n     | Blue   |   7.0 |     29 |\n     | Yellow |   5.4 |     17 |\n     |--------+-------+--------|\n     | Blue   |   4.9 |     64 |\n     | Red    |   3.9 |     51 |\n     | Yellow |   2.4 |     55 |\n     | Yellow |   6.6 |     34 |\n     |--------+-------+--------|\n     | Red    |   1.1 |     58 |\n     | Yellow |   3.4 |     51 |\n\n   The two sorted columns are ‘hline’ and ‘cölØr’.  Therefore output\nhorizontal lines separate blocks of identical ‘hline’ and ‘cölØr’:\n\n     #+begin: aggregate :table \"withhline\" :cols \"hline;^n cölØr;^a vâluε 'ra;han'\" :hline 2\n     | hline | cölØr  | vâluε | 'ra;han' |               ▲        ▲                        ▲\n     |-------+--------+-------+----------|               │        │                        │\n     |     0 | Red    |   1.3 |       41 |        ╭──────╯        ╰───────────╮            │\n     |     0 | Red    |   3.5 |       35 |        │ two sorted output columns │            │\n     |     0 | Red    |   2.6 |       84 |        ╰───────────────────────────╯            │\n     |-------+--------+-------+----------|                                                 │\n     |     0 | Yellow |   9.1 |       95 |                                                 │\n     |-------+--------+-------+----------|             ╭─────────────────────────────╮     │\n     |     1 | Blue   |   8.7 |       52 |             │ 2 means: create hlines      ├─────╯\n     |     1 | Blue   |   7.0 |       29 |             │ for buckets and sub-buckets │\n     |-------+--------+-------+----------|             ╰─────────────────────────────╯\n     |     1 | Yellow |   5.4 |       17 |\n     |-------+--------+-------+----------|╶─╮\n     |     2 | Blue   |   4.9 |       64 |  │   ╭──────────────────────────╮\n     |-------+--------+-------+----------| ╶┤   │ bucket hline=2           │\n     |     2 | Red    |   3.9 |       51 |  ├───┤ divided in 3 sub-buckets │\n     |-------+--------+-------+----------| ╶┤   │ Blue, Red, Yellow        │\n     |     2 | Yellow |   2.4 |       55 |  │   ╰──────────────────────────╯\n     |     2 | Yellow |   6.6 |       34 |  │\n     |-------+--------+-------+----------|╶─╯\n     |     3 | Red    |   1.1 |       58 |\n     |-------+--------+-------+----------|\n     |     3 | Yellow |   3.4 |       51 |\n     #+end:\n\n   And the ‘hline’ column may be discarded (but its side effect\nremains).  To do so use the ‘;<>’ specifier:\n\n     #+begin: aggregate :table \"withhline\" :cols \"hline;^n;<> cölØr;^a vâluε 'ra;han'\" :hline 2\n     | cölØr  | vâluε | 'ra;han' |\n     |--------+-------+----------|\n     | Red    |   1.3 |       41 |\n     | Red    |   3.5 |       35 |\n     | Red    |   2.6 |       84 |\n     |--------+-------+----------|\n     | Yellow |   9.1 |       95 |\n     |--------+-------+----------|\n     | Blue   |   8.7 |       52 |\n     | Blue   |   7.0 |       29 |\n     |--------+-------+----------|\n     | Yellow |   5.4 |       17 |\n     |--------+-------+----------|\n     | Blue   |   4.9 |       64 |\n     |--------+-------+----------|\n     | Red    |   3.9 |       51 |\n     |--------+-------+----------|\n     | Yellow |   2.4 |       55 |\n     | Yellow |   6.6 |       34 |\n     |--------+-------+----------|\n     | Red    |   1.1 |       58 |\n     |--------+-------+----------|\n     | Yellow |   3.4 |       51 |\n     #+end:\n\n   The ‘:hline’ parameter accepts a number:\n   • ‘:hline 0’, ‘:hline no’, ‘:hline nil’, or no ‘:hline’ mean that\n     there will be no hlines in the output.\n   • ‘:hline 1’, ‘:hline yes’, ‘:hline t’ mean that hlines will separate\n     blocks of identical rows regarding the major sorted column.  In\n     case no column is sorted, then output hlines will reflect input\n     ones.\n   • ‘:hline 2’ means that the major and the next major sorted columns\n     will be used to separate identical rows regarding those two\n     columns.\n   • ‘:hline 3’, ‘:hline 4’, ... may be specified, but they may result\n     in too much hlines.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Cells processing,  Next: Wide variety of inputs,  Prev: hlines in the output table,  Up: Top\n\n11 Cells processing\n*******************\n\n*Calc* is the standard Emacs desktop calculator.  Actual mathematical\ncomputations are handled through Calc.  This offers a lot of\nflexibility.\n\n* Menu:\n\n* Where Calc interpretation happens?::\n* Dates::\n* Durations::\n* Empty and malformed input cells::\n* Symbolic computation::\n* Intervals::\n* Error or precision forms::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Where Calc interpretation happens?,  Next: Dates,  Up: Cells processing\n\n11.1 Where Calc interpretation happens?\n=======================================\n\nExample of input table.  Besides numbers, there are cells with\nmathematical expressions like ‘20*30’, or just labels as ‘Red&Green’\nwithout any mathematical meaning.\n\n     #+name: to_Calc_or_not_to_Calc\n     | Day       | Color      | Level  |\n     |-----------+------------+--------|\n     | Monday    | Red        | 20*30  |\n     | Monday    | Blue       | 55+45  |\n     | Tuesday   | Red        | 1      |\n     | Tuesday   | Red&Green  | 2      |\n     | Tuesday   | Blue+Green | 3      |\n     | Wednesday | Red        | (27)   |\n     | Wednesday | Red        | (12+1) |\n     | Wednesday | Green      | [15]   |\n\n   Basically, Calc operates twice.  For example in the formula\n‘vsum(Level)’:\n   • Calc computes ‘Level’ for every input cell in the ‘Level’ column,\n   • then Calc computes ‘vsum()’ applied to the resulting list.\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vsum(Level)\"\n     | Day       | vsum(Level) |\n     |-----------+-------------|\n     | Monday    |         700 |\n     | Tuesday   |           6 |\n     | Wednesday |          55 |\n     #+END:\n\n   There are a few occasions were Calc computation does not happen:\n‘vcount()’ and ‘vlist(X)’.\n\n   The ‘vcount()’ sub-formula is evaluated as the number of input rows\nin each group, without Calc intervention.  However, later on Calc can\nhandle this number in a formula as this one: ‘vsum(Level)/vcount()’\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vcount() vsum(Level)/vcount()\"\n     | Day       | vcount() | vsum(Level)/vcount() |\n     |-----------+----------+----------------------|\n     | Monday    |        2 |                  350 |\n     | Tuesday   |        3 |                    2 |\n     | Wednesday |        3 |            18.333333 |\n     #+END:\n\n   And of course when input cells do not have a mathematical meaning,\nthe result may be non-sens:\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vsum(Color)\"\n     | Day       | vsum(Color)                                    |\n     |-----------+------------------------------------------------|\n     | Monday    | Red + Blue                                     |\n     | Tuesday   | Red + error(3, '\"Syntax error\") + Blue + Green |\n     | Wednesday | 2 Red + Green                                  |\n     #+END:\n\n   But it can also make sens.  The last row, which aggregate ‘Wednesday’\nrows, is computed as ‘2 Red + Green’.  This is correct.  This symbolic\nresult (as opposed to numerical result) shows the power of Calc as a\nsymbolic calculator.\n\n   The ‘vlist(X)’ formula is not handled by Calc at all.  This formula\nmust appear alone (not embedded as part of a bigger formula).  The cells\n‘X’ are not interpreted by Calc.  As a result, ‘vlist(X)’ produces a\ncell which concatenates input cells verbatim.  For instance, the input\ncell ‘20*30’ is left as-is.\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day vlist(Color) vlist(Level)\"\n     | Day       | vlist(Color)               | vlist(Level)       |\n     |-----------+----------------------------+--------------------|\n     | Monday    | Red, Blue                  | 20*30, 55+45       |\n     | Tuesday   | Red, Red&Green, Blue+Green | 1, 2, 3            |\n     | Wednesday | Red, Red, Green            | (27), (12+1), [15] |\n     #+END:\n\n   As a contrast, the formula ‘(Level)’ yields a list processed through\nCalc.  For instance, the ‘20*30’ formula is replaced by ‘600’.\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day (Color) (Level)\"\n     | Day       | (Color)                                        | (Level)        |\n     |-----------+------------------------------------------------+----------------|\n     | Monday    | [Red, Blue]                                    | [600, 100]     |\n     | Tuesday   | [Red, error(3, '\"Syntax error\"), Blue + Green] | [1, 2, 3]      |\n     | Wednesday | [Red, Red, Green]                              | [27, 13, [15]] |\n     #+END:\n\n   Here we used parenthesis in ‘(Color)’ and ‘(Level)’ because otherwise\nthey would have been _key columns_.  Instead of parenthesis, we can\nembed such expressions in formulas, like ‘Level+1’:\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day Level+1\"\n     | Day       | Level+1        |\n     |-----------+----------------|\n     | Monday    | [601, 101]     |\n     | Tuesday   | [2, 3, 4]      |\n     | Wednesday | [28, 14, [16]] |\n     #+END:\n\n   To summarize, a column name embedded in a formula is evaluated as the\nlist of input cells, processed by Calc.  Except for the ‘vlist(Column)’\nformula where input cells are kept verbatim.\n\n   By the way, what is the meaning of the expression ‘Level*Level’?  For\n‘Monday’, it is ‘[600,100]*[600,100]’.  Then Calc simplifies that as a\n_vector product_: sum of individual products.  ‘600^2+100^2’\n\n     #+BEGIN: aggregate :table \"to_Calc_or_not_to_Calc\" :cols \"Day Level*Level Level+Level\"\n     | Day       | Level*Level | Level+Level    |\n     |-----------+-------------+----------------|\n     | Monday    |      370000 | [1200, 200]    |\n     | Tuesday   |          14 | [2, 4, 6]      |\n     | Wednesday |        1123 | [54, 26, [30]] |\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Dates,  Next: Durations,  Prev: Where Calc interpretation happens?,  Up: Cells processing\n\n11.2 Dates\n==========\n\nSome aggregations are possible on dates.  Example.  Here is a source\ntable containing dates:\n\n     #+tblname: datetable\n     | Date                   |\n     |------------------------|\n     | [2035-12-22 Sat 09:01] |\n     | [2034-11-24 Fri 13:04] |\n     | [2030-09-24 Tue 13:54] |\n     | [2027-09-25 Sat 03:54] |\n     | [2023-02-26 Sun 16:11] |\n     | [2020-03-17 Tue 03:51] |\n     | [2018-08-21 Tue 00:00] |\n     | [2012-12-25 Tue 00:00] |\n\n   Here are the earliest and the latest dates, along with the average of\nall input dates:\n\n     #+BEGIN: aggregate :table datetable :cols \"vmin(Date) vmax(Date) vmean(Date)\"\n     | vmin(Date)             | vmax(Date)             | vmean(Date) |\n     |------------------------+------------------------+-------------|\n     | <2012-12-25 Tue 00:00> | <2035-12-22 Sat 09:01> |   739448.44 |\n     #+END:\n\n   The average of all dates is a number?  Actually, it is a date\nexpressed as the number of days since ‘[0000-12-31 Sun 00:00]’.  To\nforce a number of days to be interpreted as a date, use the ‘date()’\nfunction:\n\n     #+BEGIN: aggregate :table datetable :cols \"date(vmean(Date))\"\n     | date(vmean(Date))      |\n     |------------------------|\n     | <2025-07-16 Wed 10:29> |\n     #+END:\n\n   With the ‘date()’ function in mind, all kinds of dates handling can\nbe done.  Example: the average of earliest and the latest dates is\ndifferent from the average of all dates:\n\n     #+BEGIN: aggregate :table datetable :cols \"date(vmean(vmin(Date),vmax(Date))) date(vmean(Date))\"\n     | date(vmean(vmin(Date),vmax(Date))) | date(vmean(Date))      |\n     |------------------------------------+------------------------|\n     | <2024-06-23 Sun 16:30>             | <2025-07-16 Wed 10:29> |\n     #+END:\n\n   Note that ‘date()’ is not special to OrgAggregate.  It can be used in\nOrg Mode spreadsheet formulas.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Durations,  Next: Empty and malformed input cells,  Prev: Dates,  Up: Cells processing\n\n11.3 Durations\n==============\n\nIn Org Mode spreadsheet, durations have the forms ‘HH:MM’ or ‘HH:MM:SS’.\nIn OrgAggregate, when an input cell have one of those two forms, it is\nconverted into a number of seconds.  For instance, ‘01:00’ is converted\ninto ‘3600’ and ‘00:00:07’ is converted into ‘7’.\n\n   There may be a single digit for hours, as in ‘7:12’ or more than two\nas in ‘1255:45:00’.\n\n   To output such a form, use a formatter: ‘;T’; ‘;t’, ‘;U’.  For\nexample, we have 3 durations as input, and we want the average of them:\n\n     #+name: some_durations\n     |      dur |\n     |----------|\n     | 07:45:30 |\n     |    13:55 |\n     |    17:12 |\n\n     #+BEGIN: aggregate :table \"some_durations\" :cols \"vmean(dur) vmean(dur);T vmean(dur);t vmean(dur);U\"\n     | vmean(dur) | vmean(dur) | vmean(dur) | vmean(dur) |\n     |------------+------------+------------+------------|\n     |      46650 |   12:57:30 |      12.96 |      12:57 |\n     #+END:\n\n   • With no formatter, we get a number of seconds\n   • The ‘T’ formatter outputs the result as ‘HH:MM:SS’\n   • The ‘U’ formatter outputs the result as ‘HH:MM’\n   • The ‘t’ formatter converts the result into a number of hours (it\n     divides the number of seconds by 3600, and displays only two digits\n     after dot)\n\n   The Calc syntax for durations is also recognized:\n     HH@ MM'\n     HH@ MM' SS\"\n   Example:\n\n     #+name: calc_durations\n     | dur        |\n     |------------|\n     | 07@ 45' 30 |\n     | 13@ 55'    |\n     | 17@ 12'    |\n\n     #+BEGIN: aggregate :table \"calc_durations\" :cols \"vmean(dur)\"\n     | vmean(dur)   |\n     |--------------|\n     | 12@ 57' 30.\" |\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Empty and malformed input cells,  Next: Symbolic computation,  Prev: Durations,  Up: Cells processing\n\n11.4 Empty and malformed input cells\n====================================\n\nThe input table may contain malformed mathematical text.  For instance,\na cell containing ‘5+’ is malformed, because an expression is missing\nafter the ‘+’ symbol.  In this case, the value will be replaced by\n‘error(2, '\"Expected a number\")’ which will appear in the aggregated\ntable, signaling the problem.\n\n   An input cell may be empty.  In this case, it may be ignored or\nconverted to zero, depending on modifier flags ‘E’ and ‘N’.\n\n   The empty cells treatment\n   • makes no difference for ‘vsum’ and ‘count’.\n   • may result in zero for ‘prod’,\n   • change ‘vmean’ result,\n   • change ‘vmin’ and ‘vmax’, a possibly empty list of values resulting\n     in ‘inf’ or ‘-inf’\n\n   Some aggregation functions operate on two columns.  If the two\ncolumns have empty values at different locations, then they should be\ninterpreted as zero with the ‘NE’ modifier, otherwise the result will be\ninconsistent.\n\n   Sometimes an input table may be malformed, with incomplete rows, like\nthis one:\n\n     | Color | Level | Quantity | Day       |\n     |-------+-------+----------+-----------|\n     | Red   |    30 |       11 | Monday    |\n     | Blue  |    25 |        3 | Monday    |\n     |\n     | Blue  |    33 |       18 | Tuesday   |\n     | Red   |    27 |\n     | Blue  |    12 |       16 | Wednesday |\n     | Blue  |    15 |       15 |\n     |\n\n   Missing cells are handled as though they were empty.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Symbolic computation,  Next: Intervals,  Prev: Empty and malformed input cells,  Up: Cells processing\n\n11.5 Symbolic computation\n=========================\n\nThe computations are based on Calc, which is a symbolic calculator.\nThus, symbolic computations are built-in.  Example:\n\n   This is the source table:\n\n     #+NAME: symtable\n     | Day       | Color |  Level | Quantity |\n     |-----------+-------+--------+----------|\n     | Monday    | Red   |   30+x |     11+a |\n     | Monday    | Blue  | 25+3*x |        3 |\n     | Tuesday   | Red   | 51+2*x |       12 |\n     | Tuesday   | Red   |   45-x |       15 |\n     | Tuesday   | Blue  |     33 |       18 |\n     | Wednesday | Red   |     27 |       23 |\n     | Wednesday | Blue  |   12+x |       16 |\n     | Wednesday | Blue  |     15 |   15-6*a |\n     | Thursday  | Red   |     39 |   24-5*a |\n     | Thursday  | Red   |     41 |       29 |\n     | Thursday  | Red   |   49+x |   30+9*a |\n     | Friday    | Blue  |      7 |      5+a |\n     | Friday    | Blue  |      6 |        8 |\n     | Friday    | Blue  |     11 |        9 |\n\n   And here is the aggregated, symbolic result:\n\n     #+BEGIN: aggregate :table \"symtable\" :cols \"Day vmean(Level) vsum(Quantity)\"\n     | Day       | vmean(Level)          | vsum(Quantity) |\n     |-----------+-----------------------+----------------|\n     | Monday    | 2. x + 27.5           | a + 14         |\n     | Tuesday   | 0.333333333334 x + 43 | 45             |\n     | Wednesday | x / 3 + 18            | 54 - 6 a       |\n     | Thursday  | x / 3 + 43.           | 4 a + 83       |\n     | Friday    | 8                     | a + 22         |\n     #+END\n\n   Symbolic calculations are correctly performed on ‘x’ and ‘a’, which\nare symbolic (as opposed to numeric) expressions.\n\n   Note that if there are empty cells in the input, they will be changed\nto ‘nan’ _not a number_, and the whole aggregation will yield ‘nan’.\nThis is probably not the expected result.\n\n   The ‘N’ modifier (see *note Org Mode compatible formatters::) won’t\nhelp, because even though it will replace empty cells with zero, it will\ndo the same for anything which does not look like a number.  The best is\nto just avoid empty cells when dealing with symbolic calculations.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Intervals,  Next: Error or precision forms,  Prev: Symbolic computation,  Up: Cells processing\n\n11.6 Intervals\n==============\n\nOrg Mode tables and OrgAggregate backend engine being Emacs Calc,\nintervals are seamlessly handled.  An interval is made of two numerical\nvalues separated by two dots.\n\n   Example of a spreadsheet.  The third column is computed by\nmultiplying the first two:\n\n     #+name: int\n     | 3..5 |    10 | (30 .. 50) |\n     | 3..5 | -1..2 | (-5 .. 10) |\n     |    5 |     2 | 10         |\n     #+TBLFM: $3=$1*$2\n\n   Example of an aggregation.  The sum of the third column is computed,\nresulting in an interval:\n\n     #+BEGIN: aggregate :table \"int\" :cols \"vsum($3)\"\n     | vsum($3)   |\n     |------------|\n     | (35 .. 70) |\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Error or precision forms,  Prev: Intervals,  Up: Cells processing\n\n11.7 Error or precision forms\n=============================\n\nIn Emacs Calc, an error form is a numerical value along with its\nprecision, both values separated by ‘+/-’.\n\n   The separation may also be the Unicode character ‘±’.  In this\nspreadsheet example, the second column is 10 times the first:\n\n     #+name: cert\n     | 12±2 | 120 +/- 20 |\n     | 55±3 | 550 +/- 30 |\n     | 9±0  | 90         |\n     | 15±1 | 150 +/- 10 |\n     | 21±1 | 210 +/- 10 |\n     #+TBLFM: $2=10*$1\n\n   Now, in the following example, OrgAggregate computes the sum of the\nsecond column:\n\n     #+BEGIN: aggregate :table \"cert\" :cols \"vsum($2);f2\"\n     | vsum($2)       |\n     |----------------|\n     | 1120 +/- 38.73 |\n     #+END:\n\n   The computation made by Emacs Calc assumes that all input values\nfollow a Gaussian distribution, and are independent variables.  Then it\napplies the textbook formula for combining Gaussian distributions.\nBeware that your input values may not be independent with each other.\nIn this case, the resulting error will be slightly off (too small).\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Wide variety of inputs,  Next: Post-processing,  Prev: Cells processing,  Up: Top\n\n12 Wide variety of inputs\n*************************\n\nAs in any other Org Mode source block, the input table may come from\nseveral places.  OrgAggregate adds even more kinds of input.\n\n   Here we discus the ‘:table’ parameter.\n\n* Menu:\n\n* Standard Org Mode input::\n* Virtual input table from Babel::\n* An Org ID::\n* CSV input::\n* JSON input::\n* Input slicing::\n* Thecond filter: The cond filter.\n* Virtual input columns::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Standard Org Mode input,  Next: Virtual input table from Babel,  Up: Wide variety of inputs\n\n12.1 Standard Org Mode input\n============================\n\nThe parameter after ‘:table’ may be:\n\n   • ‘mytable’: an ordinary Org Mode table in the same buffer, named\n     ‘mytable’.\n\n   • ‘/some/dir/file.org:mytable’: an ordinary Org Mode table named\n     ‘mytable’, in a distant Org file named ‘/some/dir/file.org’.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Virtual input table from Babel,  Next: An Org ID,  Prev: Standard Org Mode input,  Up: Wide variety of inputs\n\n12.2 Virtual input table from Babel\n===================================\n\n   • ‘mybabel’: an Org Mode Babel block named ‘mybabel’ in the current\n     buffer, generating a table as its output, written in any language.\n\n   • ‘mybabel(param1=123,param2=456)’: passing parameters to an Org Mode\n     Babel block named ‘mybabel’ in the current buffer, generating a\n     table as its output, written in any language.\n\n   • ‘/some/dir/file.org:mybabel(param1=123,param2=456)’: an Org Mode\n     Babel block named ‘mybabel’ in a distant org file named\n     ‘/some/dir/file.org’, called with parameters.\n\n   The input table may be the result of executing a Babel script.  In\nthis case, the table is virtual in the sense that is appears nowhere.\n\n   (Babel is the Org Mode infrastructure to run scripts in any language,\nlike Python, R, C++, Java, D, Rust, shell, whatever, with inputs and\noutputs connected to Org Mode).\n\n   Example:\n\n   Here is a script in Emacs Lisp which creates an Org Mode table.\n\n     #+name: ascript\n     #+begin_src elisp :colnames yes\n     `(\n       (\"label\" value)                       ; cells are symbols or strings\n       hline\n       ,@(cl-loop\n          for i from 10 to 20\n          collect\n          (list\n           (format \"lbl-%c\" (+ ?A (% i 3)))  ; cell is a string\n           i)))                              ; cell is a number\n     #+end_src\n\n   If executed, the script would output this table:\n\n     #+RESULTS: ascript\n     | label | value |\n     |-------+-------|\n     | lbl-B |    10 |\n     | lbl-C |    11 |\n     | lbl-A |    12 |\n     | lbl-B |    13 |\n     | lbl-C |    14 |\n     | lbl-A |    15 |\n     | lbl-B |    16 |\n     | lbl-C |    17 |\n     | lbl-A |    18 |\n     | lbl-B |    19 |\n     | lbl-C |    20 |\n\n   But instead, OrgAggregate will execute the script and consume its\noutput:\n\n     #+BEGIN: aggregate :table \"ascript\" :cols \"label vsum(value)\"\n     | label | vsum(value) |\n     |-------+-------------|\n     | lbl-B |          58 |\n     | lbl-C |          62 |\n     | lbl-A |          45 |\n     #+END:\n\n   Here the parameter ‘:table’ specifies the name of the script to be\nexecuted.\n\n   *Beware* of the ‘:results code’ parameter.  It does not work as an\ninput for OrgAggregate.  This is because in this case the Babel script\nreturns a string, not a table.  Example:\n\n     '((a b c)\n       hline\n       (1 2 3))\n\n   Use ‘:results table’ or nothing instead.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: An Org ID,  Next: CSV input,  Prev: Virtual input table from Babel,  Up: Wide variety of inputs\n\n12.3 An Org ID\n==============\n\n   • ‘34cbc63a-c664-471e-a620-d654b26ffa31’: an identifier of an Org\n     Mode sub-tree.  The sub-tree is supposed to contain an Org table\n     (which is retrieved) or a Babel script (which is executed).\n\n   Those Org Mode identifiers span all known Org Mode files.  (Therefore\nthere is no need to specify a file).  To add such an identifier, put the\ncursor on the heading of the sub-tree, and type ‘M-x org-id-get-create’.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: CSV input,  Next: JSON input,  Prev: An Org ID,  Up: Wide variety of inputs\n\n12.4 CSV input\n==============\n\nCVS input is specific to OrgAggregate.  (Native Org Mode does not offers\nthose formats).\n\n   • ‘/some/dir/file.csv:(csv params…)’: a comma-separated-values file\n     in the CSV format, in the file ‘/some/dir/file.csv’.\n\n   • ‘name(csv params…)’: CSV formatted data within an Org block named\n     ‘\"name\"’, in the current file.\n\n   • ‘/some/dir/file.org:name(csv params…)’: CSV formatted data within\n     an Org block named ‘\"name\"’, in a distant Org file.\n\n   The cells separators in the CSV files may be TAB, comma, or\nsemicolon, they are guessed and different separators may be mixed.\n\n   Any empty row in the CSV file is interpreted as an horizontal\nseparator (‘hline’ in Org table parlance).\n\n   Parameters may be:\n   • ‘header’: the first row in the CSV file is interpreted as a header\n     containing the column names.\n   • ‘colnames (column1 column2 column3 …)’: the column names are given\n     explicitly, in case the CSV file contains only data, no header.\n   In any case, the columns may be references as ‘$1, $2, $3, …’ as\nusual.\n\n   When data is in an Org Mode file, it is supposed to be within a named\nblock of any kind.  Example with a \"quote\" block:\n\n     #+name: mycsvdata\n     #+begin_quote\n     label,quantity\n     yellow,27\n     red,-61\n     yellow,41\n     red,-29\n     red,-17\n     #+end_quote\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: JSON input,  Next: Input slicing,  Prev: CSV input,  Up: Wide variety of inputs\n\n12.5 JSON input\n===============\n\n   • ‘/some/dir/file.json:(json params…)’: a file containing a JSON\n     formatted table, in the file ‘/some/dir/file.csv’.\n\n   • ‘name(json params…)’: JSON formatted data within an Org block named\n     ‘\"name\"’, in the current file.\n\n   • ‘/some/dir/file.org:name(json params…)’: JSON formatted data within\n     an Org block named ‘\"name\"’, in a distant Org file.\n\n   The accepted formats are a vector of vectors, or a vector of\nhash-objects.\n\n   Currently, no ‘params’ are recognized.\n\n   Example of a vector of vectors:\n\n     [\n       [\"mon\",12,\"red\"  ],\n       [\"thu\",34,\"blue\" ],\n       [\"wed\",27,\"green\"],\n       [\"wed\",21,\"red\"  ],\n       [\"mon\", 7,\"blue\" ],\n       …\n     ]\n\n   Example of a vector of hash-objects:\n\n     [\n       {\"day\":\"mon\", \"quty\":12, \"color\":\"red\"  },\n       {\"day\":\"thu\", \"quty\":34, \"color\":\"blue\" },\n       {\"quty\":27, \"day\":\"wed\", \"color\":\"green\"},\n       {\"quty\":21, \"color\":\"red\", \"day\":\"wed\"  },\n       {\"day\":\"mon\", \"quty\": 7, \"color\":\"blue\" },\n       …\n     ]\n\n   In the case of hash-objects, the keys are collected as the header of\nthe resulting table, in the order seen in the JSON file.  In each\nhash-object, the order of key-value pairs is irrelevant.\n\n   A header may be specified in the JSON file as a first vector,\nfollowed by an hline (horizontal line).  Example:\n\n     [\n       [\"day\",\"quty\",\"color\"],\n       \"hline\",\n       [\"mon\",12,\"red\"  ],\n       [\"thu\",34,\"blue\" ],\n       …\n     ]\n\n   Horizontal lines may be ‘\"hline\"’, ‘[]’, ‘{}’, or ‘null’.\n\n   It is possible to mix both formats: vectors and hash-objects.\nExample:\n\n     [\n       [\"quty\",\"color\"],                         // incomplete header\n       null,                                     // horizontal line\n       {\"day\":\"mon\", \"quty\":12, \"color\":\"red\" }, // an hash-object\n       [\"thu\", 34, \"blue\" ],                     // a vector\n       …\n     ]\n\n   When data is in an Org Mode file, it is supposed to be within a named\nblock of any kind.  Example with a \"src\" block for JavaScript:\n\n     #+name: myjsondata\n     #+begin_src js\n     [\n       [\"day\",\"quty\",\"color\"],\n       \"hline\",\n       [\"mon\",12,\"red\"  ],\n       [\"thu\",34,\"blue\" ],\n       …\n     ]\n     #+end_src\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Input slicing,  Next: The cond filter,  Prev: JSON input,  Up: Wide variety of inputs\n\n12.6 Input slicing\n==================\n\nOrg Mode also provides for table slicing.  All of the previous\nreferences may be followed by an optional slicing.  Examples:\n\n   • ‘mytable[0:5]’: retain only the first 6 rows of the input table; if\n     the table has a header, then it counts as 2 rows (the header and\n     the separation line).  In this example, it would retain rows 0 and\n     1 for the header, and rows 2,3,4,5 for the content.\n\n   • ‘mytable[*,0:1]’: retain only the first 2 columns.\n\n   • ‘mytable[0:5,0:1]’: retain only the first 6 rows and the first 2\n     columns.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: The cond filter,  Next: Virtual input columns,  Prev: Input slicing,  Up: Wide variety of inputs\n\n12.7 The :cond filter\n=====================\n\nThis parameter is optional.  If present, it specifies a Lisp expression\nwhich tells whether or not a row should be kept.  When the expression\nevaluates to nil, the row is discarded.\n\n   Examples of useful expressions includes:\n   • ‘:cond (equal Color \"Red\")’\n        • to keep only rows where ‘Color’ is ‘Red’\n   • ‘:cond (> (string-to-number Quantity) 19)’\n        • to keep only rows for which ‘Quantity’ is more than ‘19’\n        • note the call to ‘string-to-number’; without this call,\n          ‘Quantity’ would be used as a string\n   • ‘:cond (> (* (string-to-number Level) 2.5) (string-to-number\n     Quantity))’\n        • to keep only rows for which ‘2.5*Level > Quantity’\n\n   Beware with this example: ‘:cond (equal Color \"Red\")’.  The input\ntable should not have a column named ‘Red’, otherwise the condition will\nmean: _keep only rows with the same value in columns Color and Red_\n\n   As a special case, when ‘:cols’ parameter is not given, the result is\nthe same as ‘:cols \"COL1 COL2 COL3...\"’.  All columns in the input table\nare specified as key columns, and output in the resulting table.\n\n   This is useful when just filtering.  But be aware that aggregation\nstill occurs.  So duplicate input rows appear only once in the result.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Virtual input columns,  Prev: The cond filter,  Up: Wide variety of inputs\n\n12.8 Virtual input columns\n==========================\n\nWhat if we want to aggregate on months?  But the input table contains\nonly plain dates.  Example:\n\n     #+name: without-months\n     | Date             | Quantity |\n     |------------------+----------|\n     | [2037-03-12 thu] |    56.93 |\n     | [2037-03-25 wed] |    20.99 |\n     | [2037-04-07 tue] |    80.81 |\n     | [2037-04-20 mon] |    22.26 |\n     | [2037-05-03 sun] |    69.75 |\n     | [2037-05-16 sat] |    39.91 |\n     | [2037-05-29 fri] |    93.13 |\n     | [2037-06-11 thu] |    17.11 |\n     | [2037-06-24 wed] |    24.21 |\n     | [2037-07-07 tue] |    82.38 |\n     | [2037-07-20 mon] |    39.94 |\n     | [2037-08-02 sun] |    81.90 |\n     | [2037-08-15 sat] |    71.67 |\n     | [2037-08-28 fri] |    82.81 |\n     | [2037-09-10 thu] |    42.50 |\n     | [2037-09-23 wed] |    62.52 |\n     | [2037-10-06 tue] |     5.13 |\n\n   We would like to specify the aggregation over ‘month(Date)’.  But\nonly plain columns may be used as aggregating keys.\n\n   One way is to add a ‘Month’ column to the input table.  The modified\ntable looks like:\n\n     #+name: with-months\n     | Date             | Quantity | Month |\n     |------------------+----------+-------|\n     | [2037-03-12 thu] |    56.93 |     3 |\n     | [2037-03-25 wed] |    20.99 |     3 |\n     | [2037-04-07 tue] |    80.81 |     4 |\n     | [2037-04-20 mon] |    22.26 |     4 |\n     | [2037-05-03 sun] |    69.75 |     5 |\n     | [2037-05-16 sat] |    39.91 |     5 |\n     | [2037-05-29 fri] |    93.13 |     5 |\n     | [2037-06-11 thu] |    17.11 |     6 |\n     | [2037-06-24 wed] |    24.21 |     6 |\n     | [2037-07-07 tue] |    82.38 |     7 |\n     | [2037-07-20 mon] |    39.94 |     7 |\n     | [2037-08-02 sun] |    81.90 |     8 |\n     | [2037-08-15 sat] |    71.67 |     8 |\n     | [2037-08-28 fri] |    82.81 |     8 |\n     | [2037-09-10 thu] |    42.50 |     9 |\n     | [2037-09-23 wed] |    62.52 |     9 |\n     | [2037-10-06 tue] |     5.13 |    10 |\n     #+TBLFM: $3=month($1)\n\n   OrgAggregate allows adding input columns like this computed ‘Month’\ncolumn, without modifying the input table.  The ‘:precompute’ parameter\ndoes that.  Example:\n\n     #+BEGIN: aggregate :table \"without-months\" :cols \"Month vsum(Quantity)\" :precompute (\"month(Date);'Month'\")\n     | Month | vsum(Quantity) |\n     |-------+----------------|\n     |     3 |          77.92 |\n     |     4 |         103.07 |\n     |     5 |         202.79 |\n     |     6 |          41.32 |\n     |     7 |         122.32 |\n     |     8 |         236.38 |\n     |     9 |         105.02 |\n     |    10 |           5.13 |\n     #+END:\n\n   The specification ‘month(Date);'Month'’ means:\n   • add a third column to the input table,\n   • fill it with the formula ‘month(Date)’, which is a Calc formula,\n   • give this new column the ‘Month’ title,\n   • make this new column available for aggregation, as any other\n     column.\n   All this process is virtual.  The input table is not modified in any\nway.\n\n   If the title ‘Month’ is not specified, then the new virtual column\nwill be referred to as ‘$3’.\n\n   Note that here the title was specified with single quotes.  This is\nrequired to disambiguate with the format.  The syntax is consistent with\nthe one used in the ‘:cols’ parameter and the one used by Org Mode\nspreadsheet formulas.\n\n   The pre-computations may also be Lisp expressions, exactly like in\nthe usual Org table spreadsheet.  In this example, we want to aggregate\non coarse bins.  Bins are just hundredths of the first column:\n\n     #+name: want-bins\n     | 109 | 41.24 |\n     | 140 | 40.60 |\n     | 174 |  7.68 |\n     | 288 | 33.96 |\n     | 334 | 21.42 |\n     | 418 | 74.73 |\n     | 455 | 79.62 |\n     | 479 | 22.23 |\n     | 554 | 28.03 |\n     | 678 | 64.12 |\n     | 797 | 70.91 |\n     | 947 | 93.48 |\n\n     #+BEGIN: aggregate :table \"want-bins\" :cols \"$3 vmean($2)\" :precompute (\"'(floor (/ (string-to-number $1) 100))\")\n     | $3 | vmean($2) |\n     |----+-----------|\n     |  1 |     29.84 |\n     |  2 |     33.96 |\n     |  3 |     21.42 |\n     |  4 |     58.86 |\n     |  5 |     28.03 |\n     |  6 |     64.12 |\n     |  7 |     70.91 |\n     |  9 |     93.48 |\n     #+END:\n\n   Virtual columns may be formatted as any other column, with the same\nsyntax as in ‘:cols’ or in the Org table spreadsheet.  For example here\nwe give it 2 digits after dot:\n\n     #+BEGIN: aggregate :table \"want-bins\" :cols \"$3\" :precompute \"floor($1/100);%.2f\"\n     |   $3 |\n     |------|\n     | 1.00 |\n     | 2.00 |\n     | 3.00 |\n     | 4.00 |\n     | 5.00 |\n     | 6.00 |\n     | 7.00 |\n     | 9.00 |\n     #+END:\n\n   Of course, those additional virtual input columns may be used for\nother purposes than key columns.  They may enter in aggregating\nformulas.  Or they may be used by the optional row filter (the ‘:cond’\nparameter).  There is no difference between actual and virtual columns.\n\n   The ‘:precompute’ parameter may be:\n\n   • a list of strings, example:\n     (\"month(Date);'Month'\" \"day(Date);'Day'\")\n\n   • a single string with fields separated by ‘::’, like in the\n     ‘#+tblfm:’ tags of a spreadsheet.  Example:\n     \"month(Date);'Month' :: day(Date);'Day'\"\n\n   • a string containing a single formula (actually this is a special\n     case of the previous one).  Example:\n     \"month(Date);'Month'\"\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Post-processing,  Next: Pull & Push,  Prev: Wide variety of inputs,  Up: Top\n\n13 Post-processing\n******************\n\nAfter OrgAggregate has generated the output table, it can be further\nprocessed:\n   • additional columns may be added with the standard Org Mode\n     spreadsheet formulas.\n   • any algorithm in an exotic language (Python, R, C++, Emacs Lisp,\n     and so on) can be applied to the output.\n\n* Menu:\n\n* Spreadsheet formulas::\n* Algorithm post processing::\n* Grand total::\n* Chaining::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Spreadsheet formulas,  Next: Algorithm post processing,  Up: Post-processing\n\n13.1 Spreadsheet formulas\n=========================\n\nAdditional columns can be specified for the resulting table.  With a\nprevious example, adding a ‘:formula’ parameter, we specify a new column\n‘$4’ which uses the aggregated columns.  It is translated into a usual\n‘#+TBLFM:’ spreadsheet line.\n\n     #+BEGIN: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\" :formula \"$4=$2*$3\"\n     | Day       | vmean(Level) | vsum(Quantity) |      |\n     |-----------+--------------+----------------+------|\n     | Monday    |         27.5 |             14 | 385. |\n     | Tuesday   |           43 |             45 | 1935 |\n     | Wednesday |           18 |             54 |  972 |\n     | Thursday  |           43 |             83 | 3569 |\n     | Friday    |            8 |             22 |  176 |\n     #+TBLFM: $4=$2*$3\n     #+END:\n\n   Moreover, if a ‘#+TBLFM:’ was already there, it survives aggregation\nre-computations.\n\n   This happens in _pull mode_ only.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Algorithm post processing,  Next: Grand total,  Prev: Spreadsheet formulas,  Up: Post-processing\n\n13.2 Algorithm post processing\n==============================\n\nThe aggregated table can be post-processed with the ‘:post’ parameter.\nIt accepts a Lisp ‘lambda’, a Lisp function, or a Babel block in any\nexotic language (R, Python, C++, Emacs Lisp and so on).\n\n   The process receives the aggregated table as parameter in the form of\na Lisp expression.  It can process it in any way it wants, provided it\nreturns a valid Lisp table.\n\n   A Lisp table is a list of rows.  Each row is either a list of cells,\nor the special symbol ‘hline’.\n\n   In this example, a ‘lambda’ expression adds a ‘hline’ and a row for\n_Sunday_.\n\n     #+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\" :post (lambda (table) (append table '(hline (Sunday \"0.0\"))))\n     | Day       | vsum(Quantity) |\n     |-----------+----------------|\n     | Monday    |             14 |\n     | Tuesday   |             45 |\n     | Wednesday |             54 |\n     | Thursday  |             83 |\n     | Friday    |             22 |\n     |-----------+----------------|\n     | Sunday    |            0.0 |\n     #+END:\n\n   The ‘lambda’ can be moved to a ‘defun’.  The function is then passed\nto the ‘:post’ parameter:\n\n     #+begin_src elisp\n     (defun my-function (table)\n       (append table\n               '(hline (Sunday \"0.0\"))))\n     #+end_src\n\n     ... :post my-function\n\n   The ‘:post’ parameter can also refer to a Babel Block.  Example:\n\n     #+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\" :post \"my-babel-block(tbl=*this*)\"\n     ...\n     #+END:\n\n     #+name: my-babel-block\n     #+begin_src elisp :var tbl=\"\"\n     (append tbl\n             '(hline (Sunday \"0.0\")))\n     #+end_src\n\n   *Beware!*  You may want to add ‘:colnames t’ to your Babel block.\nOtherwise the table’s header will be lost.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Grand total,  Next: Chaining,  Prev: Algorithm post processing,  Up: Post-processing\n\n13.3 Grand total\n================\n\nShe (the user) may be tempted to add the grand total at the bottom of\nthe aggregation.  Example of such a temptation:\n\n     #+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n     | Day       | vmean(Level) | vsum(Quantity) |\n     |-----------+--------------+----------------|\n     | Monday    |         27.5 |             14 |\n     | Tuesday   |           43 |             45 |\n     | Wednesday |           18 |             54 |\n     | Thursday  |           43 |             83 |\n     | Friday    |            8 |             22 |\n     |-----------+--------------+----------------|\n     | Total     |              |            218 |\n     #+TBLFM: @7$3=vsum(@I..@II)\n     #+end\n\n   With OrgAggreagate post-processing, it is easy.\n  1. Just put in place a small algorithm to add two lines.\n\n     :post (append *this* '(hline (\"Total\" \"\" \"\")))\n                     ▲        ▲               ▲\n                     ╰─────╮  │               │\n     the aggregated table╶─╯  │               │\n     one horizontal line╶─────╯               │\n     an empty cell to recieve the total╶──────╯\n\n   The ‘*this*’ Lisp variable contains the aggregated table, just while\nthe post-processing takes place.  The ‘append’ Lisp function adds two\nrows to the aggregated table, and returns the amended table.\n\n   Note that the ‘:post’ parameter may be:\n   • a small Lisp expression, as in this example,\n   • a lambda expression, which parameter is the aggregated table,\n   • the name of a Lisp function, which is passed the aggregate table,\n   • the name of a Babel block, written in any supported language.\n\n   • Fill the additional cell with a formula for the total.\n\n     @>$2=vsum(@I..@II)\n     ▲ ▲   ▲   ▲   ▲\n     │ │   │   │   ╰──────────────╮\n     │ │   │   ╰────────────────╮ │\n     │ │   ╰─────────────────╮  │ │\n     │ ╰────────────╮        │  │ │\n     ╰──────────╮   │        │  │ │\n     last line╶─╯   │        │  │ │\n     second column╶─╯        │  │ │\n     sum all values between╶─╯  │ │\n     the first horizontal line╶─╯ │\n     and the second one╶──────────╯\n\n   In this way, the grand-total will be recomputed each time the\naggregation is refreshed (‘C-c C-c’).  Note the use of ‘@>$2’ for the\ncoordinates of the cell receiving the total, instead of, for instance\n‘@7$3’.  This ensures that the formula will continue to be applied on\nthe last row, even if the aggregated table grows later on.  The same\nidea applies for the ‘@I..@II’ range, instead of, for instance ‘@2..@6’.\n\n   Even though OrgAggregate offers the user a versatil post-processing\nto add a grand total, there are many reasons not to.  If she does, she\nis quietly entering the same nightmare which plagues spreadsheets.\nEverything will become harder and harder to maintain.\n\n   It seems natural to add a grand total right below the column.  But\nsuppose that now she also want the standard deviation of this same\ncolumn.  Where to put it?  There is a blank cell just left of the grand\ntotal.  She puts the standard deviation there:\n\n     #+begin: aggregate :table original :cols \"Day vmean(Level) vsum(Quantity)\"\n     | Day       | vmean(Level) | vsum(Quantity) |\n     |-----------+--------------+----------------|\n     | Monday    |         27.5 |             14 |\n     | Tuesday   |           43 |             45 |\n     | Wednesday |           18 |             54 |\n     | Thursday  |           43 |             83 |\n     | Friday    |            8 |             22 |\n     |-----------+--------------+----------------|\n     | Total     |    27.409852 |            218 |\n     #+TBLFM: @7$3=vsum(@I..@II)::@7$2=vsdev(@I$3..@II$3)\n     #+end\n\n   But then this ‘27.409852’ looks like the grand total of the second\ncolumn, but it is not.\n\n   Later on, she may want to further process this aggregated table, for\nexample to plot it.  The grand total row will be an annoyance.  It will\nbe tedious to get ride of it.\n\n   She should instead consider creating a second aggregation with the\ngrand total:\n\n     #+begin: aggregate :table original :cols \"vsdev(Level) vsum(Quantity)\"\n     | vsdev(Level) | vsum(Quantity) |\n     |--------------+----------------|\n     |    27.409852 |            218 |\n     #+end\n\n   Easy, maintainable, no awkward decisions to remember and document.\nShe keeps it simple.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Chaining,  Prev: Grand total,  Up: Post-processing\n\n13.4 Chaining\n=============\n\nThe result of an aggregation may become the source of further\nprocessing.  To do that, just add a ‘#+NAME:’ or ‘#+TBLNAME:’ line just\nabove the aggregated table.  Here is an example of a double aggregation:\n\n     #+NAME: squantity\n     #+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\"\n     | Day       | SQuantity |\n     |-----------+-----------|\n     | Monday    |        14 |\n     | Tuesday   |        45 |\n     | Wednesday |        54 |\n     | Thursday  |        83 |\n     | Friday    |        22 |\n     #+TBLFM: @1$2=SQuantity\n     #+END:\n\n     #+BEGIN: aggregate :table \"squantity\" :cols \"vsum(SQuantity)\"\n     | vsum(SQuantity) |\n     |-----------------|\n     |             218 |\n     #+END:\n\n   Note the spreadsheet cell formula ‘@1$2=SQuantity’, which changes the\ncolumn heading from it default ‘vsum(Quantity)’ to ‘SQuantity’.  This\nnew heading will survive any refresh.\n\n   Sometimes the name of the aggregated table is not found by some babel\nblock referencing it (Gnuplot blocks are among them).  To fix that, just\nexchange the ‘#+NAME:’ and ‘#+BEGIN:’ lines:\n\n     #+BEGIN: aggregate :table original :cols \"Day vsum(Quantity)\"\n     #+NAME: squantity\n     | Day       | SQuantity |\n     |-----------+-----------|\n     | Monday    |        14 |\n     | Tuesday   |        45 |\n     | Wednesday |        54 |\n     | Thursday  |        83 |\n     | Friday    |        22 |\n     #+TBLFM: @1$2=SQuantity\n     #+END:\n\n   The ‘#.NAME:’ line will survive when recomputing the aggregation (as\n‘#.TBLFM:’ line survives)\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Pull & Push,  Next: Debugging,  Prev: Post-processing,  Up: Top\n\n14 Pull & Push\n**************\n\nTwo modes are available: _pull_ & _push_.\n\n* Menu:\n\n* Pull mode::\n* Push mode::\n* Pull or push ?::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Pull mode,  Next: Push mode,  Up: Pull & Push\n\n14.1 Pull mode\n==============\n\nIn the _pull_ mode, we use so called _\"dynamic blocks\"_.  The resulting\ntable knows how to build itself.\n\n   Example:\n\n   We have a source table which is unaware that it will be derived in an\naggregated table:\n\n     #+NAME: source1\n     | Day       | Color | Level | Quantity |\n     |-----------+-------+-------+----------|\n     | Monday    | Red   |    30 |       11 |\n     | Monday    | Blue  |    25 |        3 |\n     | Tuesday   | Red   |    51 |       12 |\n     | Tuesday   | Red   |    45 |       15 |\n     | Tuesday   | Blue  |    33 |       18 |\n     | Wednesday | Red   |    27 |       23 |\n     | Wednesday | Blue  |    12 |       16 |\n     | Wednesday | Blue  |    15 |       15 |\n     | Thursday  | Red   |    39 |       24 |\n     | Thursday  | Red   |    41 |       29 |\n     | Thursday  | Red   |    49 |       30 |\n     | Friday    | Blue  |     7 |        5 |\n     | Friday    | Blue  |     6 |        8 |\n     | Friday    | Blue  |    11 |        9 |\n\n   We create somewhere else a _dynamic block_ which carries the\nspecification of the aggregation:\n\n     #+BEGIN: aggregate :table \"source1\" :cols \"Day vmean(Level) vsum(Quantity)\"\n     | Day       | vmean(Level) | vsum(Quantity) |\n     |-----------+--------------+----------------|\n     | Monday    |         27.5 |             14 |\n     | Tuesday   |           43 |             45 |\n     | Wednesday |           18 |             54 |\n     | Thursday  |           43 |             83 |\n     | Friday    |            8 |             22 |\n     #+END\n\n   Typing ‘C-c C-c’ in the dynamic block recomputes it freshly.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Push mode,  Next: Pull or push ?,  Prev: Pull mode,  Up: Pull & Push\n\n14.2 Push mode\n==============\n\nIn _push_ mode, the source table drives the creation of derived tables.\nWe specify the wanted results in ‘#+ORGTBL: SEND’ directives (as many as\ndesired):\n\n     #+ORGTBL: SEND derived1 orgtbl-to-aggregated-table :cols \"vmean(Level) vsum(Quantity)\"\n     #+ORGTBL: SEND derived2 orgtbl-to-aggregated-table :cols \"Day vmean(Level) vsum(Quantity)\"\n     | Day       | Color | Level | Quantity |\n     |-----------+-------+-------+----------|\n     | Monday    | Red   |    30 |       11 |\n     | Monday    | Blue  |    25 |        3 |\n     | Tuesday   | Red   |    51 |       12 |\n     | Tuesday   | Red   |    45 |       15 |\n     | Tuesday   | Blue  |    33 |       18 |\n     | Wednesday | Red   |    27 |       23 |\n     | Wednesday | Blue  |    12 |       16 |\n     | Wednesday | Blue  |    15 |       15 |\n     | Thursday  | Red   |    39 |       24 |\n     | Thursday  | Red   |    41 |       29 |\n     | Thursday  | Red   |    49 |       30 |\n     | Friday    | Blue  |     7 |        5 |\n     | Friday    | Blue  |     6 |        8 |\n     | Friday    | Blue  |    11 |        9 |\n\n   We must create the receiving blocks somewhere else in the same file:\n\n     #+BEGIN RECEIVE ORGTBL derived1\n     #+END RECEIVE ORGTBL derived1\n\n     #+BEGIN RECEIVE ORGTBL derived2\n     #+END RECEIVE ORGTBL derived2\n\n   Then we come back to the source table and type ‘C-c C-c’ with the\ncursor on the 1st pipe of the table, to refresh the derived tables:\n\n     #+BEGIN RECEIVE ORGTBL derived1\n     |  vmean(Level) | vsum(Quantity) |\n     |---------------+----------------|\n     | 27.9285714286 |            218 |\n     #+END RECEIVE ORGTBL derived1\n\n     #+BEGIN RECEIVE ORGTBL derived2\n     | Day       | vmean(Level) | vsum(Quantity) |\n     |-----------+--------------+----------------|\n     | Monday    |         27.5 |             14 |\n     | Tuesday   |           43 |             45 |\n     | Wednesday |           18 |             54 |\n     | Thursday  |           43 |             83 |\n     | Friday    |            8 |             22 |\n     #+END RECEIVE ORGTBL derived2\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Pull or push ?,  Prev: Push mode,  Up: Pull & Push\n\n14.3 Pull or push ?\n===================\n\nPull & push modes use the same engine in the background.  Thus, using\neither is just a matter of convenience.\n\n   Pull mode is the most straightforward.  Also the wizard operates on\nthe pull mode only.  Almost all examples in this documentation are in\npull mode.  If you cannot decide, just use the pull mode.\n\n   Glitch: in push mode you may see strange output like ‘\\_{}’.  This is\nan escape generated by Org Mode (nothing to do with OrgAggregate).  It\nhappens for the following characters: ‘&%#_^’ To disable that, in the\n‘#+ORGTBL: SEND’ line, add this parameter: ‘:no-escape true’\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Debugging,  Next: Tricks,  Prev: Pull & Push,  Up: Top\n\n15 Debugging\n************\n\nThe work of OrgAggregate is to hand out pieces of the input table to\nCalc (the Emacs calculator).\n\n   Is some intricate cases, it may be useful to see what is going on.\nThe debugging formatters come handy.\n\n* Menu:\n\n* Seeing the $ forms::\n* Seeing Calc formulas before evaluation::\n* Seeing Lisp internal form of Calc formulas::\n* Example of debugging vsum(nn^2)::\n* Summary of debugging formatters::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Seeing the $ forms,  Next: Seeing Calc formulas before evaluation,  Up: Debugging\n\n15.1 Seeing the $ forms\n=======================\n\nHere is an example input table:\n\n     #+name: inputdebug\n     |   nn | aa |\n     |------+----|\n     | 1.23 | a  |\n     | 7.65 | b  |\n     | 8.46 | c  |\n     |------+----|\n     | 2.44 | d  |\n     | 6.81 | e  |\n\n   And here is an aggregation to debug:\n\n     #+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10) vsum(aa+7)\"\n     | hline | vsum(nn*10) | vsum(aa+7)     |\n     |-------+-------------+----------------|\n     |     0 |       173.4 | a + b + c + 21 |\n     |     1 |        92.5 | d + e + 14     |\n     #+END:\n\n   So far so good.  But we would like to know what Calc did.  To do so\nlet us add the ‘c’ formatter.\n\n     #+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10);c vsum(aa+7);c\"\n     | hline | vsum(nn*10) | vsum(aa+7) |\n     |-------+-------------+------------|\n     |     0 | vsum($1*10) | vsum($2+7) |\n     |     1 | vsum($1*10) | vsum($2+7) |\n     #+END:\n\n   Each output cell now contains the formula, with column names replaced\nby dollar equivalent forms.  But without any further processing.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Seeing Calc formulas before evaluation,  Next: Seeing Lisp internal form of Calc formulas,  Prev: Seeing the $ forms,  Up: Debugging\n\n15.2 Seeing Calc formulas before evaluation\n===========================================\n\nLet us go one step further with the ‘C’ formatter:\n\n     #+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10);C vsum(aa+7);C\"\n     | hline | vsum(nn*10)                 | vsum(aa+7)          |\n     |-------+-----------------------------+---------------------|\n     |     0 | vsum([1.23, 7.65, 8.46] 10) | vsum([a, b, c] + 7) |\n     |     1 | vsum([2.44, 6.81] 10)       | vsum([d, e] + 7)    |\n     #+END:\n\n   The dollar forms were replaced by Calc vectors made of input cells.\nNo foldings or simplifications went on.  The vectors are slices of\ncolumns, selected by OrgAggregate in response of the ‘hline’\naggregation.\n\n   We see that multiplying by ‘10’ or adding ‘7’ is done on a Calc\nvector.  It happens that Calc knows how to multiply or add something to\na vector.  OrgAggregate does not perform those operations, it delegates\nthem to Calc.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Seeing Lisp internal form of Calc formulas,  Next: Example of debugging vsum(nn^2),  Prev: Seeing Calc formulas before evaluation,  Up: Debugging\n\n15.3 Seeing Lisp internal form of Calc formulas\n===============================================\n\nWe can also view the same results, formatted as Lisp forms (rather than\nCalc forms) with the ‘Q’ formatter:\n\n     #+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn*10);Q vsum(aa+7);Q\"\n     | hline | vsum(nn*10)                                                               | vsum(aa+7)                                                            |\n     |-------+---------------------------------------------------------------------------+-----------------------------------------------------------------------|\n     |     0 | (calcFunc-vsum (* (vec (float 123 -2) (float 765 -2) (float 846 -2)) 10)) | (calcFunc-vsum (+ (vec (var a var-a) (var b var-b) (var c var-c)) 7)) |\n     |     1 | (calcFunc-vsum (* (vec (float 244 -2) (float 681 -2)) 10))                | (calcFunc-vsum (+ (vec (var d var-d) (var e var-e)) 7))               |\n     #+END:\n\n   This is the internal, Lisp representation of Calc formulas.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Example of debugging vsum(nn^2),  Next: Summary of debugging formatters,  Prev: Seeing Lisp internal form of Calc formulas,  Up: Debugging\n\n15.4 Example of debugging vsum(nn^2)\n====================================\n\nBeware of a formula like ‘vsum(nn^2)’.  This gives the expected result,\nalthough not in the obvious way.  Let us see what happens, thanks to the\n‘C’ debugging formatter:\n\n     #+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn^2);C\"\n     | hline | vsum(nn^2)                 |\n     |-------+----------------------------|\n     |     0 | vsum([1.23, 7.65, 8.46]^2) |\n     |     1 | vsum([2.44, 6.81]^2)       |\n     #+END:\n\n   We are not summing squares.  We are squaring Calc vectors.  Calc\nbeing a mathematical tool, it interprets the product of two vectors as\nthe sum of the products element-wise, as a mathematician would do.  Then\n‘vsum’ is applied on a single resulting value.  So ‘vsum’ is useless in\nthis case.  That can be confirmed:\n\n     #+BEGIN: aggregate :table \"inputdebug\" :cols \"hline vsum(nn^2) nn^2 vprod(nn^2)\"\n     | hline | vsum(nn^2) |    nn^2 | vprod(nn^2) |\n     |-------+------------+---------+-------------|\n     |     0 |    131.607 | 131.607 |     131.607 |\n     |     1 |    52.3297 | 52.3297 |     52.3297 |\n     #+END:\n\n   Therefore, changing ‘vsum’ to ‘vprod’ does not change the result.\nThis can be unexpected.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Summary of debugging formatters,  Prev: Example of debugging vsum(nn^2),  Up: Debugging\n\n15.5 Summary of debugging formatters\n====================================\n\nTo summarize the debugging settings:\n   • ‘c’: output Calc formula\n   • ‘C’: output Calc formula with dollar forms substituted by actual\n     input data\n   • ‘q’: output Lisp formula\n   • ‘Q’: output Lisp formula with column forms substituted by actual\n     input data\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Tricks,  Next: Installation,  Prev: Debugging,  Up: Top\n\n16 Tricks\n*********\n\nThis chapter collects some tricks that may be useful.\n\n* Menu:\n\n* Sorting: Sorting (1).\n* A few lowest or highest values::\n* Span of values::\n* No aggregation::\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Sorting (1),  Next: A few lowest or highest values,  Up: Tricks\n\n16.1 Sorting\n============\n\n     #+name: trick_table_1\n     | column |\n     |--------|\n     |    677 |\n     |    713 |\n     |    459 |\n     |    537 |\n     |    881 |\n\n   When several cells of a column need to be sorted, the Calc\n‘calc-sort()’ function is handy:\n\n     #+BEGIN: aggregate :table \"trick_table_1\" :cols \"(column) sort(column)\"\n     | (column)                  | sort(column)              |\n     |---------------------------+---------------------------|\n     | [677, 713, 459, 537, 881] | [459, 537, 677, 713, 881] |\n     #+END:\n\n   • ‘(column)’ gives the list of values to aggregate, without\n     aggregating them.\n   • ‘sort(column)’ gives the same list sorted in ascending order.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: A few lowest or highest values,  Next: Span of values,  Prev: Sorting (1),  Up: Tricks\n\n16.2 A few lowest or highest values\n===================================\n\nUsed with ‘subvec()’, ‘sort()’ can retrieve the two lowest or the two\nhighest values:\n\n     #+BEGIN: aggregate :table \"trick_table_1\" :cols \"subvec(sort(column),1,3) subvec(sort(column),count()-1)\"\n     | subvec(sort(column),1,3) | subvec(sort(column),count()-1) |\n     |--------------------------+--------------------------------|\n     | [459, 537]               | [713, 881]                     |\n     #+END:\n\n   • ‘subvec(...,1,3)’ extracts the two first values: from ‘1’ to ‘3’\n     excluded.\n   • ‘subvec(...,count()-1)’ extracts the two last values, numbered\n     ‘count()-1’ and ‘count()’\n\n   And of course we may retrieve the average of the two first and the\ntwo last values:\n\n     #+BEGIN: aggregate :table \"trick_table_1\" :cols \"vmean(subvec(sort(column),1,3)) vmean(subvec(sort(column),count()-1))\"\n     | vmean(subvec(sort(column),1,3)) | vmean(subvec(sort(column),count()-1)) |\n     |---------------------------------+---------------------------------------|\n     |                             498 |                                   797 |\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Span of values,  Next: No aggregation,  Prev: A few lowest or highest values,  Up: Tricks\n\n16.3 Span of values\n===================\n\n‘vmin()’ and ‘vmax()’ can compute the span of aggregated values:\n\n     #+BEGIN: aggregate :table \"trick_table_1\" :cols \"vmin(column) vmax(column) vmax(column)-vmin(column)\"\n     | vmin(column) | vmax(column) | vmax(column)-vmin(column) |\n     |--------------+--------------+---------------------------|\n     |          459 |          881 |                       422 |\n     #+END:\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: No aggregation,  Prev: Span of values,  Up: Tricks\n\n16.4 No aggregation\n===================\n\nWhy would one want to use OrgAggregate while not aggregating?  To\nbenefit from the other features of OrgAggregate:\n   • column rearrangement\n   • sorting\n   • formatting\n   • ‘#+TBLFM’ survival\n   • row filtering\n   • preprocess\n   • postprocess\n\n   To do so, mention the virtual column ‘@#’ in ‘:cols’ and make it\ninvisible with ‘;<>’.  As ‘@#’ is different for each row, the\naggregation will consider each row as a separate group.  Therefore, no\naggregation on another column will do anything more.\n\n   For example, here we:\n   • put ‘Color’ as the first column (it is the second in the input),\n   • ignore the ‘Day’ column,\n   • sort by ‘Level’,\n   • compute ‘Quantity/7’,\n   • format it with 2 digits after dot.\n\n     #+BEGIN: aggregate :table \"original\" :cols \"@#;<> Color Level;^n vmax(Quantity/7);'Q10';f2\"\n     | Color | Level |  Q10 |\n     |-------+-------+------|\n     | Blue  |     6 | 1.14 |\n     | Blue  |     7 | 0.71 |\n     | Blue  |    11 | 1.29 |\n     | Blue  |    12 | 2.29 |\n     | Blue  |    15 | 2.14 |\n     | Blue  |    25 | 0.43 |\n     | Red   |    27 | 3.29 |\n     | Red   |    30 | 1.57 |\n     | Blue  |    33 | 2.57 |\n     | Red   |    39 | 3.43 |\n     | Red   |    41 | 4.14 |\n     | Red   |    45 | 2.14 |\n     | Red   |    49 | 4.29 |\n     | Red   |    51 | 1.71 |\n     #+END:\n\n   We used the ‘vmax()’ aggregating function on ‘Quantity/7’, because\notherwise we would get a vector with a single value.  As there is a\nsingle value, any aggregating function will do the trick: ‘vmin()’,\n‘head()’, ‘rtail()’, ‘vsum()’, ‘vprod()’, ‘vmean()’, ‘vgmean()’,\n‘vhmean()’, ‘vspan()’, ‘vmedian()’.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Installation,  Next: Authors contributors,  Prev: Tricks,  Up: Top\n\n17 Installation\n***************\n\nEmacs package on Melpa: add the following lines to your ‘.emacs’ file,\nand reload it.\n\n     (add-to-list 'package-archives '(\"melpa\" . \"http://melpa.org/packages/\") t)\n     (package-initialize)\n\n   You may also customize this variable:\n     M-x customize-variable package-archives\n\n   Then browse the list of available packages and install\n‘orgtbl-aggregate’\n     M-x package-list-packages\n\n   Alternatively, you can download the lisp file, and load it:\n\n     (load-file \"orgtbl-aggregate.el\")\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Authors contributors,  Next: Changes,  Prev: Installation,  Up: Top\n\n18 Authors, contributors\n************************\n\nAuthors\n   • Thierry Banel, tbanelwebmin at free dot fr, inception &\n     implementation.\n   • Michael Brand, Calc unleashed, ‘#+TBLFM’ survival, empty input\n     cells, formatters.\n\n   Contributors\n   • Eric Abrahamsen, non-ASCII column names\n   • Alejandro Erickson, quoting non alphanumeric column names\n   • Uwe Brauer, simpler example in documentation, take\n     org-calc-default-modes preferences into account\n   • Peking Duck, fixed obsolete letf function\n   • Bill Hunker, discovered ‘\\_{}’ escape\n   • Dirk Schmitt, surviving ‘#.NAME:’ line\n   • Dale Sedivec, case insensitive ‘#+NAME:’ tags\n   • falloutphil, underscore in column names\n   • Baudilio Tejerina, t, T, U formatters\n   • Marco Pas, bug comparing empty string\n   • wuqui, sorting output table, filtering only\n   • Nicolas Viviani, output hlines\n   • Nils Lehmann, support old versions of the rx library\n   • Shankar Rao, ‘:post’ post-processing\n   • Misohena (<https://misohena.jp/blog/author/misohena>), double width\n     Japanese characters (string-width vs.  length)\n   • Kevin Brubeck Unhammer, ignore formatting cookies\n   • Tilmann Singer, more flexibility in duration format\n   • Piotr Panasiuk, ‘#+CAPTION:’ and any tags survive\n   • Luis Miguel Hernanz, fix regex bug\n   • Jason Hemann, output column names no longer have quotes\n   • Tilmann Singer, computed aggregating bins, ‘\"month(Date)\"’ in his\n     use case\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: Changes,  Next: GPL 3 License,  Prev: Authors contributors,  Up: Top\n\n19 Changes\n**********\n\nTop: earliest change.  Bottom: latest change.\n\n   • Wizard now correctly asks for columns with ‘$1, $2...’ names when\n     table header is missing\n   • Handle tables beginning with hlines\n   • Handle non-ASCII column names\n   • ‘:formula’ parameter and ‘#+TBLFM’ survival\n   • Empty cells are ignored.\n   • Empty output upon too small input set\n   • Fix ordering of output values\n   • Aggregations formulas may now be arbitrary expressions\n   • Table headers (and the lack of) are better handled\n   • Modifiers and formatters can now be specified as in the spreadsheet\n   • Aggregation function names can optionally have a leading ‘v’, like\n     ‘sum’ & ‘vsum’\n   • Increased performance on large data sets\n   • Tables can be named with ‘#+NAME:’ besides ‘#+TBLNAME:’\n   • Document Melpa installation\n   • Support quoting of column names, like \"a.b\" or ’c/d’\n   • Disable ‘\\_{}’ escape\n   • ‘#+NAME:’ inside ‘#+BEGIN:’ survives\n   • Missing input cells handled as empty ones\n   • Back-port Org Mode ‘9.4’ speed up\n   • Increase performance when inserting result into the buffer\n   • Aligned output in push mode\n   • Added a hash-table to speedup aggregation\n   • Back-port org-table-to-lisp which is now much faster\n   • ‘vlist(X)’ now yields input cells verbatim were ‘(X)’ yields Calc\n     processed input cells\n   • Document dates handling and the ‘date()’ function\n   • Implement ‘HH:MM:SS’ durations and ‘T’, ‘t’, ‘U’ formatters\n   • Sort output\n   • Create hlines in the output\n   • Missing :cond parameter means all columns\n   • Remove ‘C-c C-x i’, use standard ‘C-c C-x x’ instead\n   • Avoid name collision between Calc functions and columns\n   • More readable & faster code\n   • Support for old versions of the rx library\n   • ‘:post’ post-processing\n   • Propagate multiple rows source header to the aggregated header\n   • Ignore data rows containing formatting cookies\n   • Follow Org Mode way of handling Calc settings in Lisp code\n   • Hours in durations are no longer restricted to 2 digits\n   • 3x speedup ‘org-table-to-lisp’ and avoid Emacs 27 to 30\n     incompatibilities\n   • ‘#+CAPTION:’ and any other tag survive inside ‘#+BEGIN:’\n   • Output column names are now stripped from quotes, better reflecting\n     input names.\n   • Table-of-contents in README.org (thanks org-make-toc)\n   • Add formatters ‘c’ ‘C’ ‘q’ ‘Q’ (useful for debugging or\n     understanding OrgAggregate)\n   • Formulas involving ‘hline’ like ‘vmean(hline*10)’ are now taken\n     into account\n   • Documentation is now integrated right into Emacs in the ‘info’\n     format.  Type ‘M-: (info \"orgtbl-aggregate\")’\n   • Input table may now be the result of a Babel script (virtual\n     table).\n   • Better handling of user errors in the ‘:post’ directive.\n   • Speedup of resulting table recalculation when there are formulas in\n     ‘#+tblfm:’ or in ‘:formula’.  The overall aggregation may be up to\n     x6 faster and ÷5 less memory hungry.\n   • Circumvent an Org Mode bug in case there are a column-formula along\n     with a cell-formula, the cell-one not being calculated.  (Bonus:\n     15% speedup).\n   • Fix issue #24: bug in date parsing.\n   • Virtual pre-computed input columns.\n   • Better explanation of the input table reference syntax, including\n     distant tables and virtual table produced by Babel blocks.\n   • Support for CSV and JSON formatted input tables.\n   • New ‘@#’ virtual column giving the number of each row, pretty much\n     like the Org table spreadsheet ‘@#’ virtual column.\n   • Header and column names can be specified for CSV input tables, as\n     well as horizontal separators (‘hline’).\n   • Aggregation of the titles of this README.\n   • New free-form wizard.\n   • Illustrate README with Uniline graphics.\n   • JSON and CSV input tables can now live inside Org Mode blocks.\n   • Example for computing a refreshable grand total.\n   • Document intervals and error-forms handling.\n   • Special columns ‘@#’ and ‘hline’ are handled by transpose.\n\n\u001f\nFile: orgtbl-aggregate.info,  Node: GPL 3 License,  Prev: Changes,  Up: Top\n\n20 GPL 3 License\n****************\n\nCopyright (C) 2013-2026 Thierry Banel\n\n   orgtbl-aggregate is free software: you can redistribute it and/or\nmodify it under the terms of the GNU General Public License as published\nby the Free Software Foundation, either version 3 of the License, or (at\nyour option) any later version.\n\n   orgtbl-aggregate is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\nGeneral Public License for more details.\n\n   You should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n\n\u001f\nTag Table:\nNode: Top209\nNode: New3098\nNode: Examples3529\nNode: A very simple example3799\nNode: Demonstrate sum and average computing6154\nNode: Example without days9492\nNode: Example of counting each combination10360\nNode: Stop reading here! 80/2013040\nNode: Name your input table13520\nNode: Create an aggregation block14173\nNode: Refresh the aggregation15095\nNode: Equivalent in SQL R Datamash el-tblfn Awk C++15701\nNode: SQL equivalent16346\nNode: R equivalent16692\nNode: Datamash equivalent17226\nNode: el-tblfn17800\nNode: Awk equivalent18467\nNode: C++ equivalent19197\nNode: Wizards20321\nNode: Guiding (traditional) wizard20617\nNode: Experimental free form wizard22786\nNode: The cols parameter24832\nNode: Names of input columns25503\nNode: Grouping specifications in cols26578\nNode: The hline column27134\nNode: The @# column29922\nNode: Aggregation formulas in cols32403\nNode: Correlation of two columns39944\nNode: [Almost) any expression can be specified41683\nNode: Column names43157\nNode: Input table with or without a header43433\nNode: Column names of the input table44826\nNode: Multiple lines header45995\nNode: Custom column names48912\nNode: Formatters51322\nNode: Org Mode compatible formatters51548\nNode: Debugging formatters53749\nNode: Discarding an output column54431\nNode: Sorting57221\nNode: Example with one sorting column57427\nNode: Several sorting columns58708\nNode: hlines in the output table61060\nNode: Output hlines depends on sorting columns61458\nNode: Example with hline 264354\nNode: Cells processing69446\nNode: Where Calc interpretation happens?69941\nNode: Dates75418\nNode: Durations77423\nNode: Empty and malformed input cells79257\nNode: Symbolic computation80927\nNode: Intervals83224\nNode: Error or precision forms84020\nNode: Wide variety of inputs85191\nNode: Standard Org Mode input85738\nNode: Virtual input table from Babel86215\nNode: An Org ID88816\nNode: CSV input89417\nNode: JSON input90938\nNode: Input slicing93339\nNode: The cond filter94062\nNode: Virtual input columns95562\nNode: Post-processing101035\nNode: Spreadsheet formulas101576\nNode: Algorithm post processing102674\nNode: Grand total104637\nNode: Chaining109459\nNode: Pull & Push111148\nNode: Pull mode111382\nNode: Push mode113085\nNode: Pull or push ?115293\nNode: Debugging116027\nNode: Seeing the $ forms116550\nNode: Seeing Calc formulas before evaluation117760\nNode: Seeing Lisp internal form of Calc formulas118894\nNode: Example of debugging vsum(nn^2)120103\nNode: Summary of debugging formatters121532\nNode: Tricks122028\nNode: Sorting (1)122306\nNode: A few lowest or highest values123121\nNode: Span of values124422\nNode: No aggregation124981\nNode: Installation126835\nNode: Authors contributors127477\nNode: Changes129105\nNode: GPL 3 License133494\n\u001f\nEnd Tag Table\n\n\u001f\nLocal Variables:\ncoding: utf-8\nEnd:\n"
  },
  {
    "path": "tests/distant-tests.org",
    "content": "# -*- coding:utf-8; -*-\n#+TITLE: Distant tables for testing Orgtbl Aggregate\nCopyright (C) 2013-2026  Thierry Banel\n\norg-aggregate is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\norg-aggregate is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n* Table by name\n\n#+name: distanttable\n| tag |   val |\n|-----+-------|\n| A   | 11.58 |\n| BB  | 98.43 |\n| CCC | 87.74 |\n| BB  | 87.97 |\n| A   | 84.32 |\n| A   | 83.79 |\n| CCC |  4.32 |\n| A   | 31.07 |\n| BB  | 70.82 |\n| BB  | 32.32 |\n| A   | 88.22 |\n| CCC |  8.61 |\n| BB  | 55.17 |\n| BB  | 52.73 |\n| A   | 33.96 |\n| CCC | 23.42 |\n| CCC | 91.62 |\n| BB  | 56.19 |\n| A   | 10.78 |\n| A   | 61.71 |\n\n* Babel by name and parameter\n\n#+name: distantbabel\n#+begin_src elisp :colnames yes :var factor=29\n(append\n (list\n  '(tag value inv)\n  'hline)\n (cl-loop\n  for i from 1 to 50\n  collect\n  (list\n   (aref [A BB CCC] (% i 3))\n   (% (* i factor) 100)\n   (/ 1000.0 (+ 999.0 i)))))\n#+end_src\n\n* Table by ID\n:PROPERTIES:\n:ID:       55ab27a2-c44b-4a14-9ba4-f6879375207d\n:END:\n\n| ref  |   val |\n|------+-------|\n| S    | 685.6 |\n| TT   | 229.1 |\n| UUU  | 945.2 |\n| VVVV | 115.6 |\n| VVVV | 859.1 |\n| TT   | 767.3 |\n| UUU  | 242.8 |\n| S    | 934.5 |\n| S    | 349.2 |\n| UUU  | 227.8 |\n| VVVV | 784.8 |\n| TT   | 225.4 |\n| VVVV | 880.6 |\n| UUU  | 228.0 |\n| TT   | 882.2 |\n| TT   | 157.4 |\n| VVVV | 504.4 |\n| S    | 521.6 |\n| TT   |  90.9 |\n\n* CSV in distant Org file\n\n#+name: distantcsv\n#+begin_quote\nlabel,quantity\njes,23\nne,-65\njes,47\nne,-22\nne,-19\n#+end_quote\n\n* JSON in distant Org file\n\n#+name: distantjson\n#+begin_src js\n[\n [ 42, \"univ\"],\n [123, \"jes\" ],\n [321, \"jes\" ],\n [111, \"jes\" ],\n [-77, \"ne\"  ],\n [-99, \"ne\"  ]\n]\n#+end_src\n"
  },
  {
    "path": "tests/geography-a.csv",
    "content": "Tokyo\tJapan\t37131070\t37173748\t\nDelhi\tIndia\t34598951\t33741241\t\nShanghai\tChina\t30365228\t29973570\t\nDhaka\tBangladesh\t24561693\t23958442\t\nCairo\tEgypt\t23095986\t22623336\t\nSao Paulo\tBrazil\t23045227\t22830606\t\nMexico City\tMexico\t22831373\t22471468\t\nBeijing\tChina\t22559204\t22155716\t\nMumbai\tIndia\t22012722\t21690477\t\nOsaka\tJapan\t18990205\t19040445\t\nChongqing\tChina\t18100872\t17809422\t\nKarachi\tPakistan\t18059805\t17637893\t\nKinshasa\tDR Congo\t17815364\t17025505\t\nLagos\tNigeria\t17124998\t16554804\t\nIstanbul\tTurkey\t16211581\t16054896\t\nKolkata\tIndia\t15904385\t15535352\t\nBuenos Aires\tArgentina\t15714124\t15634092\t\nManila\tPhilippines\t15211511\t14917612\t\nGuangzhou\tChina\t14870254\t14634025\t\nLahore\tPakistan\t14835678\t14359466\t\nTianjin\tChina\t14729021\t14417999\t\nBangalore\tIndia\t14387359\t14059333\t\nRio de Janeiro\tBrazil\t13880630\t13774171\t\nShenzhen\tChina\t13501772\t13330796\t\nMoscow\tRussia\t12768223\t12699759\t\nChennai\tIndia\t12353002\t12009954\t\nBogota\tColombia\t11779275\t11660428\t\nJakarta\tIndonesia\t11628728\t11478423\t\nLima\tPeru\t11529982\t11388304\t\nBangkok\tThailand\t11415533\t11195528\t\nParis\tFrance\t11352823\t11304387\t\nHyderabad\tIndia\t11307135\t11046182\t\nNanjing\tChina\t10208049\t9935614\t\nLuanda\tAngola\t10049628\t9648709\t\nSeoul\tSouth Korea\t10059272\t9991484\t\nChengdu\tChina\t10001659\t9809118\t\nLondon\tUnited Kingdom\t9818142\t9723207\t\nHo Chi Minh City\tVietnam\t9798896\t9541155\t\nTehran\tIran\t9738111\t9606808\t\nNagoya\tJapan\t9544065\t9544935\t\nXi-an\tChina\t9253706\t9000317\t\nAhmedabad\tIndia\t9030745\t8851159\t\nKuala Lumpur\tMalaysia\t8980578\t8832827\t\nWuhan\tChina\t8951200\t8834698\t\nSuzhou\tChina\t8588504\t8351414\t\nHangzhou\tChina\t8625267\t8418349\t\nSurat\tIndia\t8592860\t8338575\t\nDar es Salaam\tTanzania\t8529744\t8188494\t\nBaghdad\tIraq\t8154140\t7911328\t\nShenyang\tChina\t7944044\t7831541\t\nRiyadh\tSaudi Arabia\t7964688\t7848751\t\nNew York City\tUnited States\t7966324\t8100605\t\nFoshan\tChina\t7833467\t7694081\t\nDongguan\tChina\t7803482\t7693276\t\nHong Kong\tHong Kong\t7791531\t7716372\t\nPune\tIndia\t7498726\t7369021\t\nHaerbin\tChina\t7082652\t6925340\t\nSantiago\tChile\t6973392\t6953542\t\nMadrid\tSpain\t6826620\t6800842\t\nKhartoum\tSudan\t6778168\t6526345\t\nToronto\tCanada\t6513813\t6450438\t\nJohannesburg\tSouth Africa\t6436807\t6339743\t\nBelo Horizonte\tBrazil\t6360069\t6281752\t\nDalian\tChina\t6360035\t6239756\t\nQingdao\tChina\t6242353\t6098734\t\nSingapore\tSingapore\t6167759\t6115882\t\nZhengzhou\tChina\t6165031\t5992273\t\nJi nan Shandong\tChina\t6062368\t5922823\t\nAbidjan\tIvory Coast\t6054358\t5859424\t\nAddis Ababa\tEthiopia\t5961711\t5681609\t\nYangon\tMyanmar\t5829964\t5706310\t\nAlexandria\tEgypt\t5801580\t5707049\t\nNairobi\tKenya\t5772121\t5560131\t\nBarcelona\tSpain\t5751075\t5730564\t\nChittagong\tBangladesh\t5635870\t5533483\t\nHanoi\tVietnam\t5593577\t5421696\t\nSaint Petersburg\tRussia\t5577807\t5589909\t\nGuadalajara\tMexico\t5577341\t5496809\t\nAnkara\tTurkey\t5565915\t5476444\t\nFukuoka\tJapan\t5452552\t5499374\t\nMelbourne\tAustralia\t5404124\t5305432\t\nMonterrey\tMexico\t5288432\t5215757\t\nSydney\tAustralia\t5258950\t5188593\t\nUrumqi\tChina\t5116895\t5001934\t\nChangsha\tChina\t5112784\t5047816\t\nCape Town\tSouth Africa\t5064396\t4958870\t\nJiddah\tSaudi Arabia\t5015645\t4923708\t\nBrasilia\tBrazil\t4998660\t4916085\t\nKunming\tChina\t4937047\t4865282\t\nChangchun\tChina\t4900896\t4796560\t\nKabul\tAfghanistan\t4862586\t4712793\t\nYaounde\tCameroon\t4859198\t4692347\t\nHefei\tChina\t4822842\t4735500\t\nNingbo\tChina\t4780151\t4659518\t\nShantou\tChina\t4737079\t4651827\t\nKano\tNigeria\t4638799\t4481282\t\nTel Aviv\tIsrael\t4577871\t4500492\t\nNew Taipei\tTaiwan\t4570576\t4522439\t\nShijiazhuang\tChina\t4527329\t4462103\t\nJaipur\tIndia\t4406280\t4321166\t\n"
  },
  {
    "path": "tests/geography-a.json",
    "content": "[\n    [\"Tokyo\",\"Japan\",37131070,37173748],\n    [\"Delhi\",\"India\",34598951,33741241],\n    [\"Shanghai\",\"China\",30365228,29973570],\n    [\"Dhaka\",\"Bangladesh\",24561693,23958442],\n    [\"Cairo\",\"Egypt\",23095986,22623336],\n    [\"Sao Paulo\",\"Brazil\",23045227,22830606],\n    [\"Mexico City\",\"Mexico\",22831373,22471468],\n    [\"Beijing\",\"China\",22559204,22155716],\n    [\"Mumbai\",\"India\",22012722,21690477],\n    [\"Osaka\",\"Japan\",18990205,19040445],\n    [\"Chongqing\",\"China\",18100872,17809422],\n    [\"Karachi\",\"Pakistan\",18059805,17637893],\n    [\"Kinshasa\",\"DR Congo\",17815364,17025505],\n    [\"Lagos\",\"Nigeria\",17124998,16554804],\n    [\"Istanbul\",\"Turkey\",16211581,16054896],\n    [\"Kolkata\",\"India\",15904385,15535352],\n    [\"Buenos Aires\",\"Argentina\",15714124,15634092],\n    [\"Manila\",\"Philippines\",15211511,14917612],\n    [\"Guangzhou\",\"China\",14870254,14634025],\n    [\"Lahore\",\"Pakistan\",14835678,14359466],\n    [\"Tianjin\",\"China\",14729021,14417999],\n    [\"Bangalore\",\"India\",14387359,14059333],\n    [\"Rio de Janeiro\",\"Brazil\",13880630,13774171],\n    [\"Shenzhen\",\"China\",13501772,13330796],\n    [\"Moscow\",\"Russia\",12768223,12699759],\n    [\"Chennai\",\"India\",12353002,12009954],\n    [\"Bogota\",\"Colombia\",11779275,11660428],\n    [\"Jakarta\",\"Indonesia\",11628728,11478423],\n    [\"Lima\",\"Peru\",11529982,11388304],\n    [\"Bangkok\",\"Thailand\",11415533,11195528],\n    [\"Paris\",\"France\",11352823,11304387],\n    [\"Hyderabad\",\"India\",11307135,11046182],\n    [\"Nanjing\",\"China\",10208049,9935614],\n    [\"Luanda\",\"Angola\",10049628,9648709],\n    [\"Seoul\",\"South Korea\",10059272,9991484],\n    [\"Chengdu\",\"China\",10001659,9809118],\n    [\"London\",\"United Kingdom\",9818142,9723207],\n    [\"Ho Chi Minh City\",\"Vietnam\",9798896,9541155],\n    [\"Tehran\",\"Iran\",9738111,9606808],\n    [\"Nagoya\",\"Japan\",9544065,9544935],\n    [\"Xi-an\",\"China\",9253706,9000317],\n    [\"Ahmedabad\",\"India\",9030745,8851159],\n    [\"Kuala Lumpur\",\"Malaysia\",8980578,8832827],\n    [\"Wuhan\",\"China\",8951200,8834698],\n    [\"Suzhou\",\"China\",8588504,8351414],\n    [\"Hangzhou\",\"China\",8625267,8418349],\n    [\"Surat\",\"India\",8592860,8338575],\n    [\"Dar es Salaam\",\"Tanzania\",8529744,8188494],\n    [\"Baghdad\",\"Iraq\",8154140,7911328],\n    [\"Shenyang\",\"China\",7944044,7831541],\n    [\"Riyadh\",\"Saudi Arabia\",7964688,7848751],\n    [\"New York City\",\"United States\",7966324,8100605],\n    [\"Foshan\",\"China\",7833467,7694081],\n    [\"Dongguan\",\"China\",7803482,7693276],\n    [\"Hong Kong\",\"Hong Kong\",7791531,7716372],\n    [\"Pune\",\"India\",7498726,7369021],\n    [\"Haerbin\",\"China\",7082652,6925340],\n    [\"Santiago\",\"Chile\",6973392,6953542],\n    [\"Madrid\",\"Spain\",6826620,6800842],\n    [\"Khartoum\",\"Sudan\",6778168,6526345],\n    [\"Toronto\",\"Canada\",6513813,6450438],\n    [\"Johannesburg\",\"South Africa\",6436807,6339743],\n    [\"Belo Horizonte\",\"Brazil\",6360069,6281752],\n    [\"Dalian\",\"China\",6360035,6239756],\n    [\"Qingdao\",\"China\",6242353,6098734],\n    [\"Singapore\",\"Singapore\",6167759,6115882],\n    [\"Zhengzhou\",\"China\",6165031,5992273],\n    [\"Ji nan Shandong\",\"China\",6062368,5922823],\n    [\"Abidjan\",\"Ivory Coast\",6054358,5859424],\n    [\"Addis Ababa\",\"Ethiopia\",5961711,5681609],\n    [\"Yangon\",\"Myanmar\",5829964,5706310],\n    [\"Alexandria\",\"Egypt\",5801580,5707049],\n    [\"Nairobi\",\"Kenya\",5772121,5560131],\n    [\"Barcelona\",\"Spain\",5751075,5730564],\n    [\"Chittagong\",\"Bangladesh\",5635870,5533483],\n    [\"Hanoi\",\"Vietnam\",5593577,5421696],\n    [\"Saint Petersburg\",\"Russia\",5577807,5589909],\n    [\"Guadalajara\",\"Mexico\",5577341,5496809],\n    [\"Ankara\",\"Turkey\",5565915,5476444],\n    [\"Fukuoka\",\"Japan\",5452552,5499374],\n    [\"Melbourne\",\"Australia\",5404124,5305432],\n    [\"Monterrey\",\"Mexico\",5288432,5215757],\n    [\"Sydney\",\"Australia\",5258950,5188593],\n    [\"Urumqi\",\"China\",5116895,5001934],\n    [\"Changsha\",\"China\",5112784,5047816],\n    [\"Cape Town\",\"South Africa\",5064396,4958870],\n    [\"Jiddah\",\"Saudi Arabia\",5015645,4923708],\n    [\"Brasilia\",\"Brazil\",4998660,4916085],\n    [\"Kunming\",\"China\",4937047,4865282],\n    [\"Changchun\",\"China\",4900896,4796560],\n    [\"Kabul\",\"Afghanistan\",4862586,4712793],\n    [\"Yaounde\",\"Cameroon\",4859198,4692347],\n    [\"Hefei\",\"China\",4822842,4735500],\n    [\"Ningbo\",\"China\",4780151,4659518],\n    [\"Shantou\",\"China\",4737079,4651827],\n    [\"Kano\",\"Nigeria\",4638799,4481282],\n    [\"Tel Aviv\",\"Israel\",4577871,4500492],\n    [\"New Taipei\",\"Taiwan\",4570576,4522439],\n    [\"Shijiazhuang\",\"China\",4527329,4462103],\n    [\"Jaipur\",\"India\",4406280,4321166]\n]\n"
  },
  {
    "path": "tests/hline-hash.json",
    "content": "[\n    [\"bb\",\"cc\"],\n    null,\n    {\"aa\":122,\"bb\":654,\"cc\":\"apple\"},\n    {\"aa\":34,\"bb\":-78,\"cc\":\"grape\"},\n    {},\n    {\"aa\":12,\"bb\":\"+45\",\"cc\":   \"grape\"},\n    {\"aa\":24,\"bb\":\"+35\",\"cc\": \"apple\"},\n    {\"aa\":33,\"bb\":\"+66\",\"cc\": \"apple\"},\n    {\"aa\":10,\"bb\":\"+91\" ,\"cc\": \"apple\"},\n    [],\n    [-15,\"apple\",122],\n    {\"aa\":8   ,\"bb\":  7,\"cc\":  \"grape\"},\n    {\"aa\":1,\"bb\": 4  ,\"cc\":  \"grape\"}\n]\n"
  },
  {
    "path": "tests/hline-header.json",
    "content": "[\n    [\"aa\",\"bb\",\"cc\"],\n    null,\n    [122,654,\"apple\"],\n    [34,-78,\"grape\"],\n    [],\n    [12,\"+45\",   \"grape\"],\n    [24,\"+35\", \"apple\"],\n    [33,\"+66\", \"apple\"],\n    [10,\"+91\" , \"apple\"],\n    [],\n    [122,-15, \"apple\"],\n    [8   ,  7,  \"grape\"],\n    [1, 4  ,  \"grape\"]\n]\n"
  },
  {
    "path": "tests/hline.csv",
    "content": "aa;bb;cc\n122;654;apple\n34;-78;grape\n\n12;+45;   \"grape\"\n24;+35; \"apple\"\n33;+66; \"apple\"\n10;+91 ; \"apple\"\n\n122,-15, \"apple\"\n8   ,  7,  \"grape\"\n  1, 4  ,  \"grape\"\n"
  },
  {
    "path": "tests/unfoldtest.org",
    "content": "# -*- coding:utf-8; -*-\n#+TITLE: Tests for unfolding Orgtbl Aggregate \"BEGIN: aggregate\" line\nCopyright (C) 2013-2026  Thierry Banel\n\norg-aggregate is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\norg-aggregate is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n* How to run?\nRunning all tests should not change anything to this page.\n\nRun this script to complete all the unit tests in a disposable\nbuffer. When done, the buffer and the original, untouched\n~unfoldtest.org~, are compared, stopping at the first difference.\n\n#+begin_src elisp :results none\n;; define a utility to run a unit test\n(defun orgtbl-aggregate-unfold-test (&rest args)\n  (execute-kbd-macro\n   (kbd\n    (mapconcat #'identity (cons \"C-n\" args) \"\\n\"))))\n\n;; display this buffer in a window occupying half the frame\n(delete-other-windows)\n(goto-char (point-min))\n(org-cycle '(64))\n(split-window-right)\n\n;; Make a new buffer and fill it with the content of unittests.org\n(let ((f (buffer-file-name)))\n  (switch-to-buffer \"disposable-unfoldtests.org\")\n  (erase-buffer)\n  (insert-file f))\n(org-mode)\n(org-cycle '(64))\n\n;; Clean results from prior tests:\n;; replace a pair of a simple and a complex aggregate blocks\n;; with 2 copies of the simple one\n(rx-define aggregateblock\n  (group\n   \"#+begin: \" (* not-newline) \"\\n\"\n   (*? (* any) \"\\n\")\n   \"#+end:\" (* any) \"\\n\"))\n\n(save-excursion\n  (goto-char (point-min))\n  (replace-regexp\n   (rx bol aggregateblock aggregateblock)\n   \"\\\\2\\\\2\"))\n\n;; call all wizard invocations\n(goto-char (point-min))\n(org-next-visible-heading 2)\n;;(while (search-forward \"(execute-kbd-macro\" nil t)\n;;  (beginning-of-line)\n;;  (forward-sexp)\n;;  (forward-line)\n;;  (beginning-of-line)\n;;  (eval-last-sexp nil))\n\n(while (re-search-forward\n        (rx \"orgtbl-aggregate-unfold-test\"\n            (*? (* any) \"\\n\")\n            \"#+end_src\")\n        nil t)\n  (beginning-of-line)\n  (org-ctrl-c-ctrl-c))\n\n;; Compare the disposable buffer with the reference\n(goto-char (point-min))\n(compare-windows nil)\n#+end_src\n\n* unua testo\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-unfold-test\n   \"C-a <right> <tab>\"\n   \"<down> <down> <down> <down> <end> ( csv SPC header )\"\n   \"<down> [ <kp-0> : <kp-1> <kp-2> ]\"\n   \"<down> <end> a a * <kp-1> <kp-0> ; ' b b '\"\n   \"<down> C-e <left> <left> <left> <left> <left> <left> <left> v s u m ( b b ) SPC\"\n   \"<down> <down> <end> <tab> <tab>\"\n   \"<down> <end> ( l a m b d a SPC ( x ) SPC x )\"\n   \"<up> <up> <up> <up> <up> <up> <up> <up> <up> <up> <tab>\"\n   \"C-c C-c\")\n#+end_src\n#+BEGIN: aggregate :table \"hline.csv:(csv header)[0:12]\" :precompute \"aa*10;'bb'\" :cols \"vsum(aa) vsum(bb) count()\" :hline \"1\" :post \"(lambda (x) x)\"\n| vsum(aa) | vsum(bb) | count() |\n|----------+----------+---------|\n|      156 |      576 |       2 |\n|----------+----------+---------|\n|       79 |      237 |       4 |\n|----------+----------+---------|\n|      131 |       -4 |       3 |\n#+END:\n#+BEGIN: aggregate :table \"hline.csv:\" :cols \"vsum(aa) count()\" :hline \"0\"\n| vsum(aa) | count() |\n|----------+---------|\n|      366 |       9 |\n#+END:\n\n* quote quotes\n#+begin_src elisp :results none\n(orgtbl-aggregate-unfold-test\n \"C-a <right> <tab>\"\n \"C-s : p o s <left> <end> ( lambda SPC ( table ) SPC ( append SPC table SPC ' ( hline SPC ( \\\"total\\\" SPC 0 ) ) ) ) \"\n \"C-r b e g i <right> <tab>\"\n \"C-c C-c\")\n#+end_src\n#+BEGIN: aggregate :table \"distant-tests.org:distanttable\" :cols \"tag vsum(val)\" :post \"(lambda (table) (append table '(hline (\\\"total\\\" 0))))\"\n| tag   | vsum(val) |\n|-------+-----------|\n| A     |    405.43 |\n| BB    |    453.63 |\n| CCC   |    215.71 |\n|-------+-----------|\n| total |         0 |\n#+END:\n#+BEGIN: aggregate :table \"distant-tests.org:distanttable\" :cols \"tag vsum(val)\"\n| tag | vsum(val) |\n|-----+-----------|\n| A   |    405.43 |\n| BB  |    453.63 |\n| CCC |    215.71 |\n#+END:\n"
  },
  {
    "path": "tests/unittests.org",
    "content": "# -*- coding:utf-8; -*-\n#+TITLE: Unit Tests & Examples for Orgtbl Aggregate\nCopyright (C) 2013-2026  Thierry Banel\n\norg-aggregate is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\norg-aggregate is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n* How to run?\nRunning all tests should not change anything to this page.\n\nRun this script to complete all the unit tests in a disposable\nbuffer. When done, the buffer and the original, untouched\n~unittests.org~, are compared, stopping at the first difference.\n\n#+begin_src elisp :results none\n(delete-other-windows)\n(goto-char (point-min))\n(org-cycle '(64))\n(split-window-right)\n\n;; Make a new buffer and fill it with the content of unittests.org\n\n(let ((f (buffer-file-name)))\n  (switch-to-buffer \"disposable-unittest.org\")\n  (erase-buffer)\n  (insert-file f))\n\n(org-mode)\n(org-cycle '(64))\n\n;; Clean results from prior tests\n(save-excursion\n  (goto-char (point-min))\n  (replace-regexp\n   (rx (group\n        bol \"#+BEGIN\" (* not-newline) \"\\n\"\n        (* (* (any \" \\t\")) \"#+\" (* not-newline) \"\\n\"))\n       (* \"|\" (* not-newline) \"\\n\"))\n   \"\\\\1\\n\"))\n\n;; Compute all pull-mode tests\n(let ((org-calc-default-modes\n       (cons 'calc-float-format (cons '(float 12) org-calc-default-modes))))\n  (org-update-all-dblocks))\n\n;; Compute all push-mode tests\n(let ((org-calc-default-modes\n       (cons 'calc-float-format (cons '(float 12) org-calc-default-modes))))\n  (org-table-map-tables\n    (lambda ()\n      (when (save-excursion\n\t      (forward-line -1)\n\t      (looking-at-p (rx (or \"#+begin\" \"#+orgtbl\"))))\n\t(orgtbl-send-table 'maybe)))))\n\n;; Compare the disposable buffer with the reference unittests.org\n(goto-char (point-min))\n(compare-windows nil)\n  #+end_src\n\n* Test PUSH\nPush = source table drives computation of aggregated tables.\nRun by typing C-c C-c on the first pipe of the source table.\n\n** Source table\nNo need to name it.\n\n#+ORGTBL: SEND aggtable1 orgtbl-to-aggregated-table :cols \"sum($3) $2 sum($4) mean($5) $3*$3 min($5) max($5)\"\n#+ORGTBL: SEND aggtable2 orgtbl-to-aggregated-table :cols \"sum(x) q sum(y) mean(z) x*x min(z) max(z)\"\n#+ORGTBL: SEND aggtable3 orgtbl-to-aggregated-table :cols \"p count() sum($4) mean(z) sum(z*z) (x) min(y) max(y)\"\n#+ORGTBL: SEND aggtable4 orgtbl-to-aggregated-table :cols \"count() mean(x) mean(y) mean(z) meane(z) median(z)\" :cond (not (equal f \"\"))\n#+ORGTBL: SEND aggtable5 orgtbl-to-aggregated-table :cols \"count() mean(x) mean(y) mean(z) meane(z) median(z) hline\"\n#+ORGTBL: SEND aggtable6 orgtbl-to-aggregated-table :cols \"q prod(z) sdev(z) pvar(z) psdev(z)\"\n#+ORGTBL: SEND aggtable7 orgtbl-to-aggregated-table :cols \"q prod(z) cov(x,y) corr(z,z)\"\n#+ORGTBL: SEND aggtable8 orgtbl-to-aggregated-table :cols \"hline min(d) max(d) mean(d)\"\n#+ORGTBL: SEND aggtable9 orgtbl-to-aggregated-table :cols \"sum(x) q sum(y) mean(z) x*x min(z) max(z)\" :cond (equal hline 2)\n#+ORGTBL: SEND aggtablea orgtbl-to-aggregated-table :cols \"sum(x) q sum(y) mean(z) x*x min(z) max(z)\" :cond (equal q \"b\")\n| p | q |   x |    y | z | f | d                      |\n|---+---+-----+------+---+---+------------------------|\n| 1 | b |  12 |    8 | 9 | 0 | [2013-12-22 sun 09:01] |\n| 3 | b |  12 |    8 | 9 | 0 | [2013-11-23 sat 13:04] |\n| 1 | a |   3 |    2 | 4 | 0 | [2011-09-24 sat 13:54] |\n| 2 | a |   3 |    2 | 2 |   | [2013-09-25 wed 03:54] |\n| 3 | a |   3 |    2 | 1 |   | [2014-02-26 wed 16:11] |\n| 3 | a |   5 |    4 | 2 |   | [2014-01-18 sat 03:51] |\n| 1 | a | 5.1 |    2 | 8 | 1 | [2013-12-25 wed 00:00] |\n|---+---+-----+------+---+---+------------------------|\n| 2 | b |   9 |    8 | 5 |   | [2012-12-25 tue 00:00] |\n| 3 | b |   9 |    8 | 1 |   | [2014-01-18 sat 23:22] |\n| 4 | a |   a |    a | 8 |   | [2014-08-02 sat 23:22] |\n| 5 | a |   a | 10*a | 4 |   | [2015-09-14 mon 13:07] |\n|---+---+-----+------+---+---+------------------------|\n| 4 | b |   b |    b | 6 | 1 | [2015-10-02 fri 17:42] |\n| 5 | b | b+3 |  b*b | 8 | 1 | [2016-01-28 thu 15:06] |\n\n** Resulting tables\n\n#+BEGIN RECEIVE ORGTBL aggtable1\n| sum($3)    | $2 | sum($4)      |      mean($5) | $3*$3             | min($5) | max($5) |\n|------------+----+--------------+---------------+-------------------+---------+---------|\n| 2 b + 45   | b  | b + b^2 + 32 | 6.33333333333 | 2 b^2 + 6 b + 459 |       1 |       9 |\n| 2 a + 19.1 | a  | 11 a + 12    | 4.14285714286 | 2 a^2 + 78.01     |       1 |       8 |\n#+END RECEIVE ORGTBL aggtable1\n\n#+BEGIN RECEIVE ORGTBL aggtable2\n| sum(x)     | q | sum(y)       |       mean(z) | x*x               | min(z) | max(z) |\n|------------+---+--------------+---------------+-------------------+--------+--------|\n| 2 b + 45   | b | b + b^2 + 32 | 6.33333333333 | 2 b^2 + 6 b + 459 |      1 |      9 |\n| 2 a + 19.1 | a | 11 a + 12    | 4.14285714286 | 2 a^2 + 78.01     |      1 |      8 |\n#+END RECEIVE ORGTBL aggtable2\n\n#+BEGIN RECEIVE ORGTBL aggtable3\n| p | count() |    sum($4) | mean(z) | sum(z*z) | (x)           |         min(y) |         max(y) |\n|---+---------+------------+---------+----------+---------------+----------------+----------------|\n| 1 |       3 |         12 |       7 |      161 | [12, 3, 5.1]  |              2 |              8 |\n| 3 |       4 |         22 |    3.25 |       87 | [12, 3, 5, 9] |              2 |              8 |\n| 2 |       2 |         10 |     3.5 |       29 | [3, 9]        |              2 |              8 |\n| 4 |       2 |      a + b |       7 |      100 | [a, b]        |      min(a, b) |      max(a, b) |\n| 5 |       2 | 10 a + b^2 |       6 |       80 | [a, b + 3]    | min(10 a, b^2) | max(10 a, b^2) |\n#+END RECEIVE ORGTBL aggtable3\n\n#+BEGIN RECEIVE ORGTBL aggtable4\n| count() | mean(x)                 | mean(y)                         |       mean(z) | meane(z)                         | median(z) |\n|---------+-------------------------+---------------------------------+---------------+----------------------------------+-----------|\n|       6 | 0.333333333333 b + 5.85 | b / 6 + b^2 / 6 + 3.33333333333 | 7.33333333333 | 7.33333333333 +/- 0.802772971919 |         8 |\n#+END RECEIVE ORGTBL aggtable4\n\n#+BEGIN RECEIVE ORGTBL aggtable5\n| count() | mean(x)       | mean(y)         | mean(z) | meane(z)              | median(z) | hline |\n|---------+---------------+-----------------+---------+-----------------------+-----------+-------|\n|       7 | 6.15714285714 | 4               |       5 | 5 +/- 1.34518541827   |         4 |     0 |\n|       4 | 0.5 a + 4.5   | 2.75 a + 4      |     4.5 | 4.5 +/- 1.44337567297 |       4.5 |     1 |\n|       2 | b + 1.5       | b / 2 + b^2 / 2 |       7 | 7 +/- 1               |         7 |     2 |\n#+END RECEIVE ORGTBL aggtable5\n\n#+BEGIN RECEIVE ORGTBL aggtable6\n| q | prod(z) |       sdev(z) |       pvar(z) |      psdev(z) |\n|---+---------+---------------+---------------+---------------|\n| b |   19440 | 3.07679486912 | 7.88888888889 | 2.80871659106 |\n| a |    4096 | 2.85356919364 | 6.97959183673 | 2.64189171556 |\n#+END RECEIVE ORGTBL aggtable6\n\n#+BEGIN RECEIVE ORGTBL aggtable7\n| q | prod(z) | cov(x,y)                                                         | corr(z,z) |\n|---+---------+------------------------------------------------------------------+-----------|\n| b |   19440 | 0.133333333333 b^3 - 3.63333333333 b - 0.766666666667 b^2 + 19.2 |        1. |\n| a |    4096 | 1.30952380953 a^2 - 5.57380952381 a + 2.5761904762               |        1. |\n#+END RECEIVE ORGTBL aggtable7\n\n#+BEGIN RECEIVE ORGTBL aggtable8\n| hline | min(d)                 | max(d)                 |                     mean(d) |\n|-------+------------------------+------------------------+-----------------------------|\n|     0 | <2011-09-24 Sat 13:54> | <2014-02-26 Wed 16:11> | <14089-07-11 Mon 11:55> / 7 |\n|     1 | <2012-12-25 Tue 00:00> | <2015-09-14 Mon 13:07> |               735354.373438 |\n|     2 | <2015-10-02 Fri 17:42> | <2016-01-28 Thu 15:06> |               735932.683334 |\n#+END RECEIVE ORGTBL aggtable8\n\n#+BEGIN RECEIVE ORGTBL aggtable9\n| sum(x)  | q | sum(y)  | mean(z) | x*x             | min(z) | max(z) |\n|---------+---+---------+---------+-----------------+--------+--------|\n| 2 b + 3 | b | b + b^2 |       7 | 2 b^2 + 6 b + 9 |      6 |      8 |\n#+END RECEIVE ORGTBL aggtable9\n\n#+BEGIN RECEIVE ORGTBL aggtablea\n| sum(x)   | q | sum(y)       |       mean(z) | x*x               | min(z) | max(z) |\n|----------+---+--------------+---------------+-------------------+--------+--------|\n| 2 b + 45 | b | b + b^2 + 32 | 6.33333333333 | 2 b^2 + 6 b + 459 |      1 |      9 |\n#+END RECEIVE ORGTBL aggtablea\n\n* Test PULL\nPull = aggregated table knows how to compute itself,\n       source table is unaware of the aggregation.\n\n** Source table\nNot changed in any way by the aggregate process.\n(Note: non-ascii characters are used as column names)\n\n#+TBLNAME: pulledtable\n| pé | qû |  xà |   yÿ | zö | déf |\n|----+----+-----+------+----+-----|\n|  1 | b  |  12 |    8 |  9 |     |\n|  3 | b  |  12 |    8 |  9 |     |\n|  1 | a  |   3 |    2 |  4 |   1 |\n|  2 | a  |   3 |    2 |  2 |     |\n|  3 | a  |   3 |    2 |  1 |   1 |\n|  3 | a  |   5 |    4 |  2 |   1 |\n|  1 | a  | 5.1 |    2 |  8 |   1 |\n|  2 | b  |   9 |    8 |  5 |     |\n|  3 | b  |   9 |    8 |  1 |     |\n|  4 | a  |   a |    a |  8 |     |\n|  5 | a  |   a | 10*a |  4 |   1 |\n|  4 | b  |   b |    b |  6 |   1 |\n|  5 | b  | b+3 |  b*b |  8 |     |\n\n** Resulting tables\nType C-c C-c within each to refresh\n\nNote the =:formula= parameter to add a new column after the aggregation has been computed.\n\n#+BEGIN: aggregate :table pulledtable :cols (\"qû\" \"mean(zö)\") :formula \"$3=$2*100\"\n| qû |      mean(zö) |           |\n|----+---------------+-----------|\n| b  | 6.33333333333 | 633.33333 |\n| a  | 4.14285714286 | 414.28571 |\n#+TBLFM: $3=$2*100\n#+END\n\nNote the additional =$8= column automatically computed after the aggregation\n\n#+BEGIN: aggregate :table pulledtable :cols \"sum(xà) qû sum(yÿ) mean(zö) xà*xà min(zö) max(zö)\"\n| sum(xà)    | qû | sum(yÿ)      |      mean(zö) | xà*xà             | min(zö) | max(zö) |     |\n|------------+----+--------------+---------------+-------------------+---------+---------+-----|\n| 2 b + 45   | b  | b + b^2 + 32 | 6.33333333333 | 2 b^2 + 6 b + 459 |       1 |       9 |   5 |\n| 2 a + 19.1 | a  | 11 a + 12    | 4.14285714286 | 2 a^2 + 78.01     |       1 |       8 | 4.5 |\n#+TBLFM: $8=($6+$7)/2\n#+END\n\n#+BEGIN: aggregate :table pulledtable :cols \"pé count() sum($4) mean(zö) sum(zö*zö) (xà) min(yÿ) max(yÿ)\"\n#+caption: named_table\n#+attr_latex: :environment longtable :width \\linewidth\n| pé | count() |    sum($4) | mean(zö) | sum(zö*zö) | (xà)          |        min(yÿ) |        max(yÿ) |\n|----+---------+------------+----------+------------+---------------+----------------+----------------|\n|  1 |       3 |         12 |        7 |        161 | [12, 3, 5.1]  |              2 |              8 |\n|  3 |       4 |         22 |     3.25 |         87 | [12, 3, 5, 9] |              2 |              8 |\n|  2 |       2 |         10 |      3.5 |         29 | [3, 9]        |              2 |              8 |\n|  4 |       2 |      a + b |        7 |        100 | [a, b]        |      min(a, b) |      max(a, b) |\n|  5 |       2 | 10 a + b^2 |        6 |         80 | [a, b + 3]    | min(10 a, b^2) | max(10 a, b^2) |\n#+END\n\n#+BEGIN: aggregate :table pulledtable :cols \"count() mean(xà) mean(yÿ) mean(zö)\"\n| count() | mean(xà)                                            | mean(yÿ)                                             |      mean(zö) |\n|---------+-----------------------------------------------------+------------------------------------------------------+---------------|\n|      13 | 0.153846153846 a + 0.153846153846 b + 4.93076923077 | 0.846153846154 a + b / 13 + b^2 / 13 + 3.38461538462 | 5.15384615385 |\n#+END\n\n#+BEGIN: aggregate :table pulledtable :cols \"pé count() mean(zö) meane(zö) gmean(zö) hmean(zö) median(zö)\"\n| pé | count() | mean(zö) | meane(zö)              |     gmean(zö) |     hmean(zö) | median(zö) |\n|----+---------+----------+------------------------+---------------+---------------+------------|\n|  1 |       3 |        7 | 7 +/- 1.52752523165    | 6.60385449779 | 6.17142857143 |          8 |\n|  3 |       4 |     3.25 | 3.25 +/- 1.93110503771 | 2.05976714391 | 1.53191489362 |        1.5 |\n|  2 |       2 |      3.5 | 3.5 +/- 1.5            | 3.16227766017 | 2.85714285714 |        3.5 |\n|  4 |       2 |        7 | 7 +/- 1                | 6.92820323028 | 6.85714285714 |          7 |\n|  5 |       2 |        6 | 6 +/- 2                | 5.65685424949 | 5.33333333333 |          6 |\n#+END\n\n#+BEGIN: aggregate :table pulledtable :cols \"qû count() prod(zö) sdev(zö) pvar(zö) psdev(zö)\"\n| qû | count() | prod(zö) |      sdev(zö) |      pvar(zö) |     psdev(zö) |\n|----+---------+----------+---------------+---------------+---------------|\n| b  |       6 |    19440 | 3.07679486912 | 7.88888888889 | 2.80871659106 |\n| a  |       7 |     4096 | 2.85356919364 | 6.97959183673 | 2.64189171556 |\n#+END\n\n#+BEGIN: aggregate :table pulledtable :cols \"qû count() cov(zö,xà) pcov(zö,zö) corr(zö,zö)\"\n| qû | count() | cov(zö,xà)                       |   pcov(zö,zö) | corr(zö,zö) |\n|----+---------+----------------------------------+---------------+-------------|\n| b  |       6 | 0.266666666666 b + 1.8           | 7.88888888889 |          1. |\n| a  |       7 | 0.619047619047 a - 1.22142857142 | 6.97959183673 |          1. |\n#+END\n\n* Test :cond PUSH\n\n** Source table\nOnly the second group (5 rows) is considered with the test =hline=1=.\n\n#+ORGTBL: SEND aggtable15 orgtbl-to-aggregated-table :cond (equal hline 1) :cols \"count() q mean(x) mean(y) mean(z) hline\"\n| p | q |   x |    y | z |\n|---+---+-----+------+---|\n| 1 | b |  12 |    8 | 9 |\n| 3 | b |  12 |    8 | 9 |\n| 1 | a |   3 |    2 | 4 |\n| 2 | a |   3 |    2 | 2 |\n| 3 | a |   3 |    2 | 1 |\n| 3 | a |   5 |    4 | 2 |\n| 1 | a | 5.1 |    2 | 8 |\n|---+---+-----+------+---|\n| 2 | b |   9 |    8 | 5 |\n| 3 | b |   9 |    8 | 1 |\n| 4 | a |   a |    a | 8 |\n| 5 | a |   a | 10*a | 4 |\n| 4 | b |   b |    b | 6 |\n|---+---+-----+------+---|\n| 5 | b | b+3 |  b*b | 8 |\n\n** Aggregated table\n\n#+BEGIN RECEIVE ORGTBL aggtable15\n| count() | q | mean(x)   | mean(y)               | mean(z) | hline |\n|---------+---+-----------+-----------------------+---------+-------|\n|       3 | b | b / 3 + 6 | b / 3 + 5.33333333333 |       4 |     1 |\n|       2 | a | a         | 5.5 a                 |       6 |     1 |\n#+END RECEIVE ORGTBL aggtable15\n\n* Test :cond PULL\nThe =:cond= parameter takes a lisp expression\nto filter-out resulting rows.\n\n** Resulting tables\nOnly consider rows for which column q have the value \"b\"\n\n#+BEGIN: aggregate :table pulledtable :cols \"qû count() mean(zö)\" :cond (equal qû \"b\")\n| qû | count() |      mean(zö) |\n|----+---------+---------------|\n| b  |       6 | 6.33333333333 |\n#+END\n\nOnly consider rows for which column =p= is greater than =3=.\nNote the =string-to-number= call, because cells always contain strings.\n\n#+BEGIN: aggregate :table pulledtable :cols \"qû count() mean(zö)\" :cond (>= (string-to-number pé) 3)\n| qû | count() | mean(zö) |\n|----+---------+----------|\n| b  |       4 |        6 |\n| a  |       4 |     3.75 |\n#+END\n\nOnly consider rows for which the =def= column is not blank.\n\n#+BEGIN: aggregate :table pulledtable :cols \"qû count() mean(zö) déf\" :cond (not (equal déf \"\"))\n| qû | count() | mean(zö) | déf |\n|----+---------+----------+-----|\n| a  |       5 |      3.8 |   1 |\n| b  |       1 |        6 |   1 |\n#+END\n\n* Test correlation\nAre two columns correlated ?\n\n** Source table\nContains columns correlated with some noise.\n: y = 10* + noise             (x y are highly correlated)\n: z = pure noise              (x z are not correlated)\n: t = pure noise              (z t are not correlated)\n: m = 10*x in reverse order   (x m are negative correlated)\n\n#+TBLNAME: correlated\n| tag   |  x |       y |     z |     t |   m |\n|-------+----+---------+-------+-------+-----|\n| small |  1 |  10.414 | 78.30 |  1.70 | 120 |\n| small |  2 |  20.616 | 48.20 | 80.40 | 110 |\n| small |  3 |  30.210 | 93.50 | 25.10 | 100 |\n| small |  4 |  41.692 | 85.90 | 16.30 |  90 |\n| small |  5 |  50.576 | 11.70 | 37.00 |  80 |\n| large |  6 |  60.026 | 46.60 |  6.00 |  70 |\n| large |  7 |  71.236 |  3.30 | 35.70 |  60 |\n| large |  8 |  81.204 | 78.80 | 46.30 |  50 |\n| large |  9 |  90.862 | 89.60 | 98.40 |  40 |\n| large | 10 | 101.240 |  0.60 |  8.80 |  30 |\n| large | 11 | 111.924 | 32.40 | 63.70 |  20 |\n| large | 12 | 120.490 | 35.50 | 98.20 |  10 |\n\nThe following line was appended to the table to generate the random noise.\nIt is thrown away to avoid recomputing new noise, and thus invalidating the test.\n: #+TBLFM: $3=$2*10+random(1000)/500;%.3f::$4=random(1000)/10;%.2f::$5=random(1000)/10;%.2f\n\n** Resulting table\nType C-c C-c within resulting table to refresh.\n\n#+BEGIN: aggregate :table correlated :cols \"tag corr(x,y) corr(x,z) corr(x,m) corr(z,t)\"\n| tag   |      corr(x,y) |       corr(x,z) | corr(x,m) |      corr(z,t) |\n|-------+----------------+-----------------+-----------+----------------|\n| small | 0.999449791325 | -0.448296141593 |        -1 | -0.49786310458 |\n| large | 0.999657841285 | -0.120566390616 |        -1 | 0.486014333463 |\n#+END\n\n* Test without headers\nWhat if the source table does not have headers?\nThen columns should be named =$1=, =$2=, =$3= and so on.\n\n** Source table\n\n#+TBLNAME: noheader\n| 0 | z |   t |    x | y |\n| 1 | b |  12 |    8 | 9 |\n| 3 | b |  12 |    8 | 9 |\n| 1 | a |   3 |    2 | 4 |\n| 2 | a |   3 |    2 | 2 |\n| 3 | a |   3 |    2 | 1 |\n| 3 | a |   5 |    4 | 2 |\n| 1 | a | 5.1 |    2 | 8 |\n| 2 | b |   9 |    8 | 5 |\n| 3 | b |   9 |    8 | 1 |\n| 4 | a |   a |    a | 8 |\n| 5 | a |   a | 10*a | 4 |\n| 4 | b |   b |    b | 6 |\n| 5 | b | b+3 |  b*b | 8 |\n\n** Aggregated table\n\n#+BEGIN: aggregate :table noheader :cols \"hline $1 mean($3) sum($4)\"\n| hline | $1 | mean($3)            | sum($4)    |\n|-------+----+---------------------+------------|\n|     0 |  0 | t                   | x          |\n|     0 |  1 | 6.7                 | 12         |\n|     0 |  3 | 7.25                | 22         |\n|     0 |  2 | 6                   | 10         |\n|     0 |  4 | a / 2 + b / 2       | a + b      |\n|     0 |  5 | a / 2 + b / 2 + 1.5 | 10 a + b^2 |\n#+END\n\n* Test hline grouping\nHorizontal lines naturally create groups withing the source table.\nThose groups can be accessed through the =hline= virtual column.\n\n** Source table\nIt contains four groups separated by horizontal lines.\n\n#+TBLNAME: hlinetable\n| p | q |   x |    y | z | f |\n|---+---+-----+------+---+---|\n| 1 | b |  12 |    8 | 9 | 0 |\n| 3 | b |  12 |    8 | 9 | 0 |\n| 1 | a |   3 |    2 | 4 | 0 |\n| 2 | a |   3 |    2 | 2 | 0 |\n| 3 | a |   3 |    2 | 1 | 0 |\n|---+---+-----+------+---+---|\n| 3 | a |   5 |    4 | 2 | 1 |\n| 1 | a | 5.1 |    2 | 8 | 1 |\n|---+---+-----+------+---+---|\n| 2 | b |   9 |    8 | 5 | 1 |\n| 3 | b |   9 |    8 | 1 | 1 |\n| 4 | a |   a |    a | 8 | 1 |\n|---+---+-----+------+---+---|\n| 5 | a |   a | 10*a | 4 | 1 |\n| 4 | b |   b |    b | 6 | 1 |\n| 5 | b | b+3 |  b*b | 8 | 1 |\n\n** Aggregated table\nThe =hline= column groups data\n\n#+BEGIN: aggregate :table hlinetable :cols \"q hline vcount()\" :cond (equal f \"1\")\n| q | hline | vcount() |\n|---+-------+----------|\n| a |     1 |        2 |\n| b |     2 |        2 |\n| a |     2 |        1 |\n| a |     3 |        1 |\n| b |     3 |        2 |\n#+END\n\n* Test @# row numbering\n\nare a's & b's near the beginning or the end of the input table?\n#+BEGIN: aggregate :table \"hlinetable\" :cols \"q vmean(@#)\"\n| q |     vmean(@#) |\n|---+---------------|\n| b | 9.16666666667 |\n| a | 7.57142857143 |\n#+END:\n\np & q columns in reverse order\n#+BEGIN: aggregate :table \"hlinetable\" :cols \"p q hline @#;^N2;<>\"\n| p | q | hline |\n|---+---+-------|\n| 5 | b |     3 |\n| 4 | b |     3 |\n| 5 | a |     3 |\n| 4 | a |     2 |\n| 3 | b |     2 |\n| 2 | b |     2 |\n| 1 | a |     1 |\n| 3 | a |     1 |\n| 3 | a |     0 |\n| 2 | a |     0 |\n| 1 | a |     0 |\n| 3 | b |     0 |\n| 1 | b |     0 |\n#+END:\n\n#+BEGIN: aggregate :table \"hlinetable\" :cols \"p q hline @#;^N2\"\n| p | q | hline | @# |\n|---+---+-------+----|\n| 5 | b |     3 | 16 |\n| 4 | b |     3 | 15 |\n| 5 | a |     3 | 14 |\n| 4 | a |     2 | 12 |\n| 3 | b |     2 | 11 |\n| 2 | b |     2 | 10 |\n| 1 | a |     1 |  8 |\n| 3 | a |     1 |  7 |\n| 3 | a |     0 |  5 |\n| 2 | a |     0 |  4 |\n| 1 | a |     0 |  3 |\n| 3 | b |     0 |  2 |\n| 1 | b |     0 |  1 |\n#+END:\n\n#+BEGIN: transpose :table \"hlinetable\" :cols \"p q @# hline z f x y\"\n| p |   |  1 |  3 | 1 | 2 | 3 |   | 3 |   1 |   |  2 |  3 |  4 |   |    5 |  4 |   5 |\n| q |   |  b |  b | a | a | a |   | a |   a |   |  b |  b |  a |   |    a |  b |   b |\n| 0 |   |  2 |  3 | 4 | 5 | 6 |   | 8 |   9 |   | 11 | 12 | 13 |   |   15 | 16 |  17 |\n| 0 |   |  1 |  1 | 1 | 1 | 1 |   | 2 |   2 |   |  3 |  3 |  3 |   |    4 |  4 |   4 |\n| z |   |  9 |  9 | 4 | 2 | 1 |   | 2 |   8 |   |  5 |  1 |  8 |   |    4 |  6 |   8 |\n| f |   |  0 |  0 | 0 | 0 | 0 |   | 1 |   1 |   |  1 |  1 |  1 |   |    1 |  1 |   1 |\n| x |   | 12 | 12 | 3 | 3 | 3 |   | 5 | 5.1 |   |  9 |  9 |  a |   |    a |  b | b+3 |\n| y |   |  8 |  8 | 2 | 2 | 2 |   | 4 |   2 |   |  8 |  8 |  a |   | 10*a |  b | b*b |\n#+END:\n\n#+BEGIN: transpose :table \"hlinetable\" :cols \"p q @# hline z f x y\"\n#+name: thetransposed\n| p |   |  1 |  3 | 1 | 2 | 3 |   | 3 |   1 |   |  2 |  3 |  4 |   |    5 |  4 |   5 |   10 |\n| q |   |  b |  b | a | a | a |   | a |   a |   |  b |  b |  a |   |    a |  b |   b | 10 b |\n| 0 |   |  2 |  3 | 4 | 5 | 6 |   | 8 |   9 |   | 11 | 12 | 13 |   |   15 | 16 |  17 |   20 |\n| 0 |   |  1 |  1 | 1 | 1 | 1 |   | 2 |   2 |   |  3 |  3 |  3 |   |    4 |  4 |   4 |   10 |\n| z |   |  9 |  9 | 4 | 2 | 1 |   | 2 |   8 |   |  5 |  1 |  8 |   |    4 |  6 |   8 |   90 |\n| f |   |  0 |  0 | 0 | 0 | 0 |   | 1 |   1 |   |  1 |  1 |  1 |   |    1 |  1 |   1 |    0 |\n| x |   | 12 | 12 | 3 | 3 | 3 |   | 5 | 5.1 |   |  9 |  9 |  a |   |    a |  b | b+3 |  120 |\n| y |   |  8 |  8 | 2 | 2 | 2 |   | 4 |   2 |   |  8 |  8 |  a |   | 10*a |  b | b*b |   80 |\n#+TBLFM: $19=$3*10\n#+END:\n\n* Test dates [YYYY-MM-DD day. HH:MM] style\nSome (limited) handling of dates is available.\n\n** Source table\n#+tblname: datetable\n| n | d                       |\n|---+-------------------------|\n| 1 | [2013-12-22 dim. 09:01] |\n| 2 | [2013-11-23 sam. 13:04] |\n| 3 | [2011-09-24 sam. 13:54] |\n| 4 | [2013-09-25 mer. 03:54] |\n| 5 | [2014-02-26 mer. 16:11] |\n| 6 | [2014-01-18 sam. 03:51] |\n| 7 | [2013-12-25 mer. 00:00] |\n| 8 | [2012-12-25 mar. 00:00] |\n\n** Aggregated table\n\n#+BEGIN: aggregate :table datetable :cols \"min(d) max(d) min(n) max(n) mean(d)\"\n| min(d)                 | max(d)                 | min(n) | max(n) |       mean(d) |\n|------------------------+------------------------+--------+--------+---------------|\n| <2011-09-24 Sat 13:54> | <2014-02-26 Wed 16:11> |      1 |      8 | 735073.937066 |\n#+END\n\n* Test durations HH:MM:SS style\n\n** Source table\n#+name: some_durations\n|      dur |\n|----------|\n| 07:45:30 |\n|    13:55 |\n|    17:12 |\n\n#+name: some_durations_in_different_formats\n|      dur |\n|----------|\n| 01:30:01 |\n|  1:30:02 |\n|    01:30 |\n|     1:30 |\n|   100:30 |\n\n** Aggregated table\n\nTest T, U, t formatters\n\n#+BEGIN: aggregate :table \"some_durations\" :cols \"vmean(dur) vmean(dur);T vmean(dur);t vmean(dur);U\"\n| vmean(dur) | vmean(dur) | vmean(dur) | vmean(dur) |\n|------------+------------+------------+------------|\n|      46650 |   12:57:30 |      12.96 |      12:57 |\n#+END:\n\n#+BEGIN: aggregate :table \"some_durations_in_different_formats\" :cols \"vsum(dur);T\"\n| vsum(dur) |\n|-----------|\n| 106:30:03 |\n#+END\n\n* Test durations HH@ MM' SS\" style\n\n#+name: calc_durations\n| dur        |\n|------------|\n| 07@ 45' 30 |\n| 13@ 55'    |\n| 17@ 12'    |\n\n#+BEGIN: aggregate :table \"calc_durations\" :cols \"vmean(dur)\"\n| vmean(dur)   |\n|--------------|\n| 12@ 57' 30.\" |\n#+END:\n\n* Test symbolic\nThe Emacs Calc symbolic calculator is used by the aggregate package.\nTherefore, symbolic calculations are available.\n\n** Source table\nContains the variables =x= and =a=, which are not numeric.\n\n#+TBLNAME: symtable\n| Day       | Color |  Level | Quantity |\n|-----------+-------+--------+----------|\n| Monday    | Red   |   30+x |     11+a |\n| Monday    | Blue  | 25+3*x |        3 |\n| Thuesday  | Red   | 51+2*x |       12 |\n| Thuesday  | Red   |   45-x |       15 |\n| Thuesday  | Blue  |     33 |       18 |\n| Wednesday | Red   |     27 |       23 |\n| Wednesday | Blue  |   12+x |       16 |\n| Wednesday | Blue  |     15 |   15-6*a |\n| Turdsday  | Red   |     39 |   24-5*a |\n| Turdsday  | Red   |     41 |       29 |\n| Turdsday  | Red   |   49+x |   30+9*a |\n| Friday    | Blue  |      7 |      5+a |\n| Friday    | Blue  |      6 |        8 |\n| Friday    | Blue  |     11 |        9 |\n\n** Aggregated table\nResult is variabilized with =x= and =a=.\n\n#+BEGIN: aggregate :table \"symtable\" :cols \"Day mean(Level) sum(Quantity)\"\n| Day       | mean(Level) | sum(Quantity) |\n|-----------+-------------+---------------|\n| Monday    | 2 x + 27.5  | a + 14        |\n| Thuesday  | x / 3 + 43  | 45            |\n| Wednesday | x / 3 + 18  | 54 - 6 a      |\n| Turdsday  | x / 3 + 43. | 4 a + 83      |\n| Friday    | 8           | a + 22        |\n#+END\n\n* Test zero output\nThe following test produces sums which happen to be zero, either\nbecause input is empty, or by chance (1-1 = 0).\nZeros are no longer translated to empty cells.\n\n#+TBLNAME: resultzero\n| Item | Value |\n|------+-------|\n| a2   |     1 |\n| a2   |     1 |\n| a0   |    -1 |\n| a0   |     1 |\n| b2   |     2 |\n| b2   |       |\n| b0   |     0 |\n| b0   |       |\n| c    |       |\n| c    |       |\n\n#+BEGIN: aggregate :table resultzero :cols \"Item vsum(Value) vmean(Value)\"\n| Item | vsum(Value) | vmean(Value) |\n|------+-------------+--------------|\n| a2   |           2 |            1 |\n| a0   |           0 |            0 |\n| b2   |           2 |            2 |\n| b0   |           0 |            0 |\n| c    |           0 |    vmean([]) |\n#+END\n\n* Test empty inputs\nEmpty input cells are most often ignored.\n- This makes no difference for =sum= and =count=.\n- For =prod=, empty input do not result in zero.\n- For =mean=, only non-empty cells participate\n  (if empty cells were zero, they would count in the division).\n- For =min= and =max=, a possibly empty list of values is possible,\n  resulting in =inf= or =-inf=\n\nSome aggregation functions operate on two columns.\nIn this case, a pair of empty cells is ignored.\nBut a pair of an empty and a non-empty cell is\nadded to the aggregation, by replacing the missing\nvalue with zero.\n\n#+tblname: emptyinput\n| T                |  Q |   R |\n|------------------+----+-----|\n| no-blank         |  1 |  10 |\n| no-blank         |  2 |  20 |\n| no-blank         |  3 |  30 |\n| 1-left-blank     |  4 |  40 |\n| 1-left-blank     |    |  50 |\n| 1-left-blank     |  6 |  60 |\n| 1-left-blank     |  7 |  70 |\n| all-blank        |    |     |\n| all-blank        |    |     |\n| all-blank        |    |     |\n| 2-left-blank     | 11 | 110 |\n| 2-left-blank     | 12 | 120 |\n| 2-left-blank     | 13 | 130 |\n| 2-left-blank     | 14 | 140 |\n| 1-dual-blank     | 15 | 150 |\n| 1-dual-blank     |    |     |\n| 1-dual-blank     | 17 | 170 |\n| single-non-blank | 18 | 180 |\n| single-non-blank |    |     |\n| single-non-blank |    |     |\n\n#+BEGIN: aggregate :table \"emptyinput\" :cols \"T sum(Q) prod(Q) (Q) min(Q) max(Q)\"\n| T                | sum(Q) | prod(Q) | (Q)              | min(Q) | max(Q) |\n|------------------+--------+---------+------------------+--------+--------|\n| no-blank         |      6 |       6 | [1, 2, 3]        |      1 |      3 |\n| 1-left-blank     |     17 |     168 | [4, 6, 7]        |      4 |      7 |\n| all-blank        |      0 |       1 | []               |    inf |   -inf |\n| 2-left-blank     |     50 |   24024 | [11, 12, 13, 14] |     11 |     14 |\n| 1-dual-blank     |     32 |     255 | [15, 17]         |     15 |     17 |\n| single-non-blank |     18 |      18 | [18]             |     18 |     18 |\n#+END:\n\n#+BEGIN: aggregate :table \"emptyinput\" :cols \"T mean(Q) meane(Q) gmean(Q) hmean(Q)\"\n| T                |       mean(Q) | meane(Q)                         |      gmean(Q) |      hmean(Q) |\n|------------------+---------------+----------------------------------+---------------+---------------|\n| no-blank         |             2 | 2 +/- 0.577350269189             | 1.81712059283 | 1.63636363636 |\n| 1-left-blank     | 5.66666666667 | 5.66666666667 +/- 0.881917103688 | 5.51784835276 | 5.36170212766 |\n| all-blank        |     vmean([]) | vmeane([])                       |    vgmean([]) |    vhmean([]) |\n| 2-left-blank     |          12.5 | 12.5 +/- 0.645497224368          | 12.4497700445 |  12.399483871 |\n| 1-dual-blank     |            16 | 16 +/- 1                         | 15.9687194227 |       15.9375 |\n| single-non-blank |            18 | vmeane([18])                     |            18 |           18. |\n#+END:\n\n#+BEGIN: aggregate :table \"emptyinput\" :cols \"T min(Q) max(Q)\"\n| T                | min(Q) | max(Q) |\n|------------------+--------+--------|\n| no-blank         |      1 |      3 |\n| 1-left-blank     |      4 |      7 |\n| all-blank        |    inf |   -inf |\n| 2-left-blank     |     11 |     14 |\n| 1-dual-blank     |     15 |     17 |\n| single-non-blank |     18 |     18 |\n#+END:\n\n#+BEGIN: aggregate :table \"emptyinput\" :cols \"T pvar(Q) sdev(Q) psdev(Q)\"\n| T                |        pvar(Q) |       sdev(Q) |       psdev(Q) |\n|------------------+----------------+---------------+----------------|\n| no-blank         | 0.666666666667 |             1 | 0.816496580928 |\n| 1-left-blank     |  1.55555555556 | 1.52752523165 |  1.24721912893 |\n| all-blank        |      vpvar([]) |     vsdev([]) |     vpsdev([]) |\n| 2-left-blank     |           1.25 | 1.29099444874 |  1.11803398875 |\n| 1-dual-blank     |              1 | 1.41421356237 |              1 |\n| single-non-blank |              0 |   vsdev([18]) |              0 |\n#+END:\n\n#+BEGIN: aggregate :table \"emptyinput\" :cols \"T corr(Q,R);EN cov(Q,R);EN pcov(Q,R);EN\"\n| T                |                   corr(Q,R) |      cov(Q,R) |     pcov(Q,R) |\n|------------------+-----------------------------+---------------+---------------|\n| no-blank         |                           1 |            10 | 6.66666666667 |\n| 1-left-blank     |              0.625543242171 |           25. |         18.75 |\n| all-blank        | vcorr([0, 0, 0], [0, 0, 0]) |             0 |             0 |\n| 2-left-blank     |                          1. | 16.6666666667 |          12.5 |\n| 1-dual-blank     |                          1. | 863.333333333 | 575.555555556 |\n| single-non-blank |                           1 |          1080 |           720 |\n#+END:\n\n#+BEGIN: aggregate :table \"emptyinput\" :cols \"T count() (Q) (R)\"\n| T                | count() | (Q)              | (R)                  |\n|------------------+---------+------------------+----------------------|\n| no-blank         |       3 | [1, 2, 3]        | [10, 20, 30]         |\n| 1-left-blank     |       4 | [4, 6, 7]        | [40, 50, 60, 70]     |\n| all-blank        |       3 | []               | []                   |\n| 2-left-blank     |       4 | [11, 12, 13, 14] | [110, 120, 130, 140] |\n| 1-dual-blank     |       3 | [15, 17]         | [150, 170]           |\n| single-non-blank |       3 | [18]             | [180]                |\n#+END:\n\n* Test empty and non-numeric\n\n#+tblname: nonnumeric\n|  X |\n|----|\n|  1 |\n|  2 |\n| aa |\n|    |\n|  4 |\n\n#+BEGIN: aggregate :table \"nonnumeric\" :cols \"(X) (X);E (X);N (X);EN\"\n| (X)           | (X)                | (X)          | (X)             |\n|---------------+--------------------+--------------+-----------------|\n| [1, 2, aa, 4] | [1, 2, aa, nan, 4] | [1, 2, 0, 4] | [1, 2, 0, 0, 4] |\n#+END:\n\n#+BEGIN: aggregate :table \"nonnumeric\" :cols \"mean(X) mean(X);E mean(X);N mean(X);EN\"\n| mean(X)       | mean(X) | mean(X) | mean(X) |\n|---------------+---------+---------+---------|\n| aa / 4 + 1.75 |     nan |    1.75 |     1.4 |\n#+END:\n\nComparison with the spreadsheet:\n\n| 1                  |                 1 |\n| 2                  |                 2 |\n| aa                 |                aa |\n|                    |                   |\n| 4                  |                 4 |\n|--------------------+-------------------|\n| [1, 2, aa, 4]      | 0.75 + aa / 4 + 1 |\n| [1, 2, aa, nan, 4] |               nan |\n| [1, 2, 0, 4]       |              1.75 |\n| [1, 2, 0, 0, 4]    |               1.4 |\n#+TBLFM: @6$1=@1..@5 :: @7$1=@1..@5;E :: @8$1=@1..@5;N :: @9$1=@1..@5;EN :: @6$2=vmean(@1..@5) :: @7$2=vmean(@1..@5);E :: @8$2=vmean(@1..@5);N :: @9$2=vmean(@1..@5);EN\n\n* Test input errors\n\n#+tblname: inputerrors\n| A |  Q |     R |         Z | D            |\n|---+----+-------+-----------+--------------|\n| a |  3 |    10 | 2.3025851 | [2014-11-05] |\n| a | 4+ |    20 | 2.9957323 | [2014-11-21] |\n| b |  t | (88*) |    #ERROR | [2014-12-07] |\n| b |  1 |    41 | 3.7135721 | [2014-12-23] |\n| b |  2 |   111 | 4.7095302 | [2015-01-08] |\n| c |  8 |   z ' |    #ERROR |              |\n| c | 4= |     4 | 1.3862944 |              |\n#+TBLFM: $4=log($3)\n\n#+BEGIN: aggregate :table \"inputerrors\" :cols \"A sum(Q) sum(R)\"\n| A | sum(Q)                             | sum(R)                               |\n|---+------------------------------------+--------------------------------------|\n| a | error(2, '\"Expected a number\") + 3 | 30                                   |\n| b | t + 3                              | error(4, '\"Expected a number\") + 152 |\n| c | error(2, '\"Expected a number\") + 8 | error(2, '\"Syntax error\") + 4        |\n#+END:\n\n#+BEGIN: aggregate :table \"inputerrors\" :cols \"A (Q) (R)\"\n| A | (Q)                                 | (R)                                       |\n|---+-------------------------------------+-------------------------------------------|\n| a | [3, error(2, '\"Expected a number\")] | [10, 20]                                  |\n| b | [t, 1, 2]                           | [error(4, '\"Expected a number\"), 41, 111] |\n| c | [8, error(2, '\"Expected a number\")] | [error(2, '\"Syntax error\"), 4]            |\n#+END:\n\n* Test modifiers\n\nNote the blank line between tblname and the actual table\n\n#+tblname: bigprec\n\n| A  | Q     |                   N |\n|----+-------+---------------------|\n| a  | 12    |                  20 |\n| a  | t+1   |   3.000000000000007 |\n| bb | 77    |                   4 |\n| bb | 2*t   | 5.12345678987654321 |\n| bb | 2*t+1 |                   6 |\n\n#+BEGIN: aggregate :table \"bigprec\" :cols \"A sum(Q) mean(Q);FS (Q)\"\n| A  | sum(Q)   | mean(Q)      | (Q)                |\n|----+----------+--------------+--------------------|\n| a  | t + 13   | t / 2 + 13:2 | [12, t + 1]        |\n| bb | 4 t + 78 | 4:3 t + 26   | [77, 2 t, 2 t + 1] |\n#+END:\n\n#+BEGIN: aggregate :table \"bigprec\" :cols \"A sum(N);p20f18 sum(N);%.5f mean(N);f15 (N);f3\"\n| A  |                sum(N) |   sum(N) |            mean(N) | (N)           |\n|----+-----------------------+----------+--------------------+---------------|\n| a  | 23.000000000000007000 | 23.00000 | 11.500000000000000 | [20, 3.000]   |\n| bb | 15.123456789876543210 | 15.12346 |  5.041152263290000 | [4, 5.123, 6] |\n#+END:\n* Test chaining\n\nResult of an aggregation can be further processed, for example with another aggregation.\n\n** chaining 3 aggregations\nNote: header is 2 lines tall\n\n#+TBLNAME: amx\n| A  | M  |  X |\n| ~a | ~m | ~x |\n|----+----+----|\n| a  | m  |  1 |\n| a  | p  |  2 |\n| a  | m  |  3 |\n|----+----+----|\n| b  | p  |  4 |\n| b  | m  |  5 |\n| b  | p  |  6 |\n| b  | m  |  7 |\n\n#+TBLNAME: amsx\n#+BEGIN: aggregate :table \"amx\" :cols \"A M sum(X)\"\n| A  | M  | SX |\n| ~a | ~m | ~x |\n|----+----+----|\n| a  | m  |  4 |\n| a  | p  |  2 |\n| b  | p  | 10 |\n| b  | m  | 12 |\n#+TBLFM: @1$3=SX\n#+END:\n\n#+TBLNAME: asx\n#+BEGIN: aggregate :table \"amsx\" :cols \"A sum(SX)\"\n#+caption: named_table\n| A  | SSX |\n| ~a |  ~x |\n|----+-----|\n| a  |   6 |\n| b  |  22 |\n#+TBLFM: @1$2=SSX\n#+END:\n\n#+BEGIN: aggregate :table \"asx\" :cols \"sum(SSX)\"\n| sum(SSX) |\n| ~x       |\n|----------|\n| 28       |\n#+END:\n\n** chaining 2 transpositions\n\n#+TBLNAME: tamx\n#+BEGIN: transpose :table \"amx\"\n| A | ~a |   | a | a | a |   | b | b | b | b |\n| M | ~m |   | m | p | m |   | p | m | p | m |\n| X | ~x |   | 1 | 2 | 3 |   | 4 | 5 | 6 | 7 |\n#+END:\n\n#+BEGIN: transpose :table \"tamx\"\n| A  | M  |  X |\n| ~a | ~m | ~x |\n|----+----+----|\n| a  | m  |  1 |\n| a  | p  |  2 |\n| a  | m  |  3 |\n|----+----+----|\n| b  | p  |  4 |\n| b  | m  |  5 |\n| b  | p  |  6 |\n| b  | m  |  7 |\n#+END:\n\nThe double transposition is identical to the original \"amx\" table,\nincluding horizontal lines\n\n* Test funny column names\nName of columns are not unnecessarily alphanumeric words.\nThey need to be single or double quoted in formulas.\nIn a :cond lisp formula, only double quotes work.\n\n** Quoted names\n\n#+NAME: funnynames\n  # some additional ignored directives\n  # and blank lines\n\n| first column | observed;number | computed/expected |\n|--------------+-----------------+-------------------|\n| a/experiment |             2.3 |               2.4 |\n| a/experiment |            15.4 |              12.1 |\n| a/experiment |             8.2 |               6.9 |\n| b/test       |           -98.7 |               0.0 |\n| b/test       |             4.5 |               3.4 |\n| b/test       |             2.2 |               2.9 |\n| zero         |               0 |                 0 |\n\n#+BEGIN: aggregate :table \"funnynames\" :cols \"\\\"first column\\\" mean('observed;number');%.3f mean('computed/expected');%.4f\" :cond (and (>= (string-to-number \"observed;number\") 0) (not (equal \"first column\" \"zero\")))\n| first column | mean(observed;number) | mean(computed/expected) |\n|--------------+-----------------------+-------------------------|\n| a/experiment |                 8.633 |                  7.1333 |\n| b/test       |                 3.350 |                  3.1500 |\n#+END:\n\n#+BEGIN: aggregate :table \"funnynames\" :cols (\"'first column'\" \"mean('observed;number');%.3f\" \"mean('computed/expected');%.4f\") :cond \"(and (>= (string-to-number \\\"observed;number\\\") 0) (not (equal \\\"first column\\\" \\\"zero\\\")))\"\n| first column | mean(observed;number) | mean(computed/expected) |\n|--------------+-----------------------+-------------------------|\n| a/experiment |                 8.633 |                  7.1333 |\n| b/test       |                 3.350 |                  3.1500 |\n#+END:\n\n#+BEGIN: transpose :table \"funnynames\" :cols (\"first column\" \"computed/expected\" \"observed;number\")\n| first column      |   | a/experiment | a/experiment | a/experiment | b/test | b/test | b/test | zero |\n| computed/expected |   |          2.4 |         12.1 |          6.9 |    0.0 |    3.4 |    2.9 |    0 |\n| observed;number   |   |          2.3 |         15.4 |          8.2 |  -98.7 |    4.5 |    2.2 |    0 |\n#+END:\n\n#+BEGIN: transpose :table \"funnynames\" :cols \"'first column' 'computed/expected' 'observed;number'\"\n| first column      |   | a/experiment | a/experiment | a/experiment | b/test | b/test | b/test | zero |\n| computed/expected |   |          2.4 |         12.1 |          6.9 |    0.0 |    3.4 |    2.9 |    0 |\n| observed;number   |   |          2.3 |         15.4 |          8.2 |  -98.7 |    4.5 |    2.2 |    0 |\n#+END:\n\n** Non alphanumeric names\nAccepted column names which do not require quoting:\n- ascii letters\n- numbers\n- underscore _, dollar $, dot .\n- accented letters like à é\n- greek letters like α, Ω\n- northen letters like ø\n- russian letters like й\n- esperanto letters like ŭ\n\n#+NAME: non_alphanum\n| _key.$ | v_A$4lé.à.α | on.eüΩ.øйŭ | 3.14 |\n|--------+-------------+------------+------|\n| a      |         2.2 |          1 |   10 |\n| a      |         4.9 |          1 |   11 |\n| b      |         7.7 |          1 |   12 |\n| b      |         2.8 |          0 |   13 |\n| b      |         9.3 |          0 |   14 |\n| c      |         6.5 |          0 |   15 |\n| a      |         8.4 |          0 |   16 |\n| a      |         1.9 |          0 |   17 |\n| b      |         5.6 |          0 |   18 |\n| c      |         7.2 |          0 |   19 |\n\n#+BEGIN: aggregate :table \"non_alphanum\" :cols \"_key.$ vsum(v_A$4lé.à.α) vsum(on.eüΩ.øйŭ*10) vlist(on.eüΩ.øйŭ) vmean(3.14*1000)\"\n| _key.$ | vsum(v_A$4lé.à.α) | vsum(on.eüΩ.øйŭ*10) | vlist(on.eüΩ.øйŭ) | vmean(3.14*1000) |\n|--------+-------------------+---------------------+-------------------+------------------|\n| a      |              17.4 |                  20 | 1, 1, 0, 0        |            13500 |\n| b      |              25.4 |                  10 | 1, 0, 0, 0        |            14250 |\n| c      |              13.7 |                   0 | 0, 0              |            17000 |\n#+END:\n\n* Test malformed tables\nSome columns are missing in some rows\nThis is on purpose\norgaggregate should tolerate such tables\nMissing cells are handled as though they were empty\n\n#+NAME: malformed\n| Color | Level | Quantity | Day       |\n|-------+-------+----------+-----------|\n| Red   |    30 |       11 | Monday    |\n| Blue  |    25 |        3 | Monday    |\n|\n| Red   |    45 |       15 | Tuesday   |\n| Blue  |    33 |       18 | Tuesday   |\n| Red   |    27 |\n| Blue  |    12 |       16 | Wednesday |\n| Blue  |    15 |       15 |\n| Red   |    39 |       24 | Thursday  |\n| Red   |    41 |       29 | Thursday  |\n| Red   |    49 |       30 | Thursday  |\n| Blue  |     7 |        5 | Friday    |\n| Blue  |     6 |\n| Blue  |    11 |        9 | Friday    |\n\n#+BEGIN: aggregate :table \"malformed\" :cols \"Day count() sum(Quantity)\"\n| Day       | count() | sum(Quantity) |\n|-----------+---------+---------------|\n| Monday    |       2 |            14 |\n|           |       4 |            15 |\n| Tuesday   |       2 |            33 |\n| Wednesday |       1 |            16 |\n| Thursday  |       3 |            83 |\n| Friday    |       2 |            14 |\n#+END:\n\n#+BEGIN: aggregate :table \"malformed\" :cols \"Color vlist(l10)\" :precompute \"Level*10;'l10'\"\n| Color | vlist(l10)                      |\n|-------+---------------------------------|\n| Red   | 300, 450, 270, 390, 410, 490    |\n| Blue  | 250, 330, 120, 150, 70, 60, 110 |\n|       |                                 |\n#+END:\n\n* Test vlist($) vs. ($)\n\n#+name: suitableforlist\n| Day       | Color      | Level |\n|-----------+------------+-------|\n| Monday    | Red        | 20*30 |\n| Monday    | Blue       | 55+25 |\n| Tuesday   | Red        |    51 |\n| Tuesday   | Red        |    45 |\n| Tuesday   | Blue       |    33 |\n| Wednesday | Red        |    27 |\n| Wednesday | Blue       |    12 |\n| Wednesday | Green      |    15 |\n| Thursday  | Red        |    39 |\n| Thursday  | Red        |    41 |\n| Thursday  | Red+Green  |    49 |\n| Friday    | Blue       |   (7) |\n| Friday    | Blue       | (6+1) |\n| Friday    | Blue&Green |  [11] |\n\n#+BEGIN: aggregate :table \"suitableforlist\" :cols \"Day vlist(Color) (Color) vlist(Level) (Level) Level*100 Level^2\"\n| Day       | vlist(Color)           | (Color)                                 | vlist(Level)     | (Level)      | Level*100          | Level^2 |\n|-----------+------------------------+-----------------------------------------+------------------+--------------+--------------------+---------|\n| Monday    | Red, Blue              | [Red, Blue]                             | 20*30, 55+25     | [600, 80]    | [60000, 8000]      |  366400 |\n| Tuesday   | Red, Red, Blue         | [Red, Red, Blue]                        | 51, 45, 33       | [51, 45, 33] | [5100, 4500, 3300] |    5715 |\n| Wednesday | Red, Blue, Green       | [Red, Blue, Green]                      | 27, 12, 15       | [27, 12, 15] | [2700, 1200, 1500] |    1098 |\n| Thursday  | Red, Red, Red+Green    | [Red, Red, Red + Green]                 | 39, 41, 49       | [39, 41, 49] | [3900, 4100, 4900] |    5603 |\n| Friday    | Blue, Blue, Blue&Green | [Blue, Blue, error(4, '\"Syntax error\")] | (7), (6+1), [11] | [7, 7, [11]] | [700, 700, [1100]] |     219 |\n#+END:\n\n* Test sorting key alpha & numeric\n\n#+NAME: unsortedtable\n| p | q |    x | Day       | Color | Level | date                   |\n|---+---+------+-----------+-------+-------+------------------------|\n| 1 | b | 12.3 | Monday    | Red   |  2*30 | [2024-12-23 Mon 09:01] |\n| 3 | b | 12.8 | Monday    | Blue  |  5+25 | [2019-11-24 Sun 13:04] |\n| 1 | a |  3.5 | Tuesday   | Red   |    51 | [2029-09-25 Tue 13:54] |\n| 2 | a |  3.9 | Tuesday   | Red   |    45 | [2033-09-26 Mon 03:55] |\n| 3 | a |  3.5 | Tuesday   | Blue  |    33 | [2015-02-27 Fri 16:11] |\n| 3 | a |  5.7 | Wednesday | Red   |    97 | [2001-01-19 Fri 03:49] |\n| 1 | a |  5.1 | Wednesday | Blue  |    52 | [2035-12-26 Wed 00:00] |\n|---+---+------+-----------+-------+-------+------------------------|\n| 2 | b |  9.3 | Tuesday   | Red   |    39 | [2035-12-26 Wed 00:00] |\n| 3 | b |  9.3 | Thursday  | Red   |    41 | [2002-01-19 Sat 23:22] |\n| 4 | a |  1.4 | Friday    | Blue  |    79 | [2026-08-01 Sat 17:27] |\n| 5 | a |  7.5 | Friday    | Blue  |   8+9 | [2020-09-15 Tue 13:07] |\n| 4 | b |  8.2 | Thursday  | Red   |    41 | [2040-10-27 Sat 09:12] |\n|---+---+------+-----------+-------+-------+------------------------|\n| 5 | b |  1.1 | Wednesday | Red   |    62 | [2011-01-29 Sat 15:06] |\n\n#+BEGIN: aggregate :table \"unsortedtable\" :cols \"p;^n Day;^a\"\n| p | Day       |\n|---+-----------|\n| 1 | Monday    |\n| 1 | Tuesday   |\n| 1 | Wednesday |\n| 2 | Tuesday   |\n| 3 | Monday    |\n| 3 | Thursday  |\n| 3 | Tuesday   |\n| 3 | Wednesday |\n| 4 | Friday    |\n| 4 | Thursday  |\n| 5 | Friday    |\n| 5 | Wednesday |\n#+END:\n\n* Test sorting numeric expression\n\n#+BEGIN: aggregate :table \"unsortedtable\" :cols \"Day count();^N\"\n| Day       | count() |\n|-----------+---------|\n| Tuesday   |       4 |\n| Wednesday |       3 |\n| Monday    |       2 |\n| Thursday  |       2 |\n| Friday    |       2 |\n#+END:\n\n#+BEGIN: aggregate :table \"unsortedtable\" :cols \"Day vsum(Level);^n\"\n| Day       | vsum(Level) |\n|-----------+-------------|\n| Thursday  |          82 |\n| Monday    |          90 |\n| Friday    |          96 |\n| Tuesday   |         168 |\n| Wednesday |         211 |\n#+END:\n\n* Test sorting hline\n\n#+BEGIN: aggregate :table \"unsortedtable\" :cols \"hline;^N q;^a count()\"\n| hline | q | count() |\n|-------+---+---------|\n|     2 | b |       1 |\n|     1 | a |       2 |\n|     1 | b |       3 |\n|     0 | a |       5 |\n|     0 | b |       2 |\n#+END:\n\n* Test sorting dates-times\n\n#+BEGIN: aggregate :table \"unsortedtable\" :cols \"date;^T count()\"\n| date                   | count() |\n|------------------------+---------|\n| [2040-10-27 Sat 09:12] |       1 |\n| [2035-12-26 Wed 00:00] |       2 |\n| [2033-09-26 Mon 03:55] |       1 |\n| [2029-09-25 Tue 13:54] |       1 |\n| [2026-08-01 Sat 17:27] |       1 |\n| [2024-12-23 Mon 09:01] |       1 |\n| [2020-09-15 Tue 13:07] |       1 |\n| [2019-11-24 Sun 13:04] |       1 |\n| [2015-02-27 Fri 16:11] |       1 |\n| [2011-01-29 Sat 15:06] |       1 |\n| [2002-01-19 Sat 23:22] |       1 |\n| [2001-01-19 Fri 03:49] |       1 |\n#+END:\n\n* Test sorting major-minor columns\n\n#+BEGIN: aggregate :table \"unsortedtable\" :cols \"date;^t3 Color;^a2 x;^n1\"\n| date                   | Color |    x |\n|------------------------+-------+------|\n| [2011-01-29 Sat 15:06] | Red   |  1.1 |\n| [2026-08-01 Sat 17:27] | Blue  |  1.4 |\n| [2015-02-27 Fri 16:11] | Blue  |  3.5 |\n| [2029-09-25 Tue 13:54] | Red   |  3.5 |\n| [2033-09-26 Mon 03:55] | Red   |  3.9 |\n| [2035-12-26 Wed 00:00] | Blue  |  5.1 |\n| [2001-01-19 Fri 03:49] | Red   |  5.7 |\n| [2020-09-15 Tue 13:07] | Blue  |  7.5 |\n| [2040-10-27 Sat 09:12] | Red   |  8.2 |\n| [2002-01-19 Sat 23:22] | Red   |  9.3 |\n| [2035-12-26 Wed 00:00] | Red   |  9.3 |\n| [2024-12-23 Mon 09:01] | Red   | 12.3 |\n| [2019-11-24 Sun 13:04] | Blue  | 12.8 |\n#+END:\n\n* Test sorting push\n\n#+ORGTBL: SEND sortag1 orgtbl-to-aggregated-table :cols \"cölØr vsum(vâluε);^N count();^N vmean('ra;han');f3\"\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Yellow |   9.1 |     95 |\n| Red    |   2.6 |     84 |\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Yellow |   5.4 |     17 |\n| Blue   |   4.9 |     64 |\n| Red    |   3.9 |     51 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n| Red    |   1.1 |     58 |\n\n#+BEGIN RECEIVE ORGTBL sortag1\n| cölØr  | vsum(vâluε) | count() | vmean(ra;han) |\n|--------+-------------+---------+---------------|\n| Yellow |        23.5 |       4 |        50.250 |\n| Blue   |        20.6 |       3 |        48.333 |\n| Red    |        12.4 |       5 |        53.800 |\n#+END RECEIVE ORGTBL sortag1\n\n* Test hline output\n\n#+name: withhline\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Yellow |   9.1 |     95 |\n| Red    |   2.6 |     84 |\n|--------+-------+--------|\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Yellow |   5.4 |     17 |\n|--------+-------+--------|\n| Blue   |   4.9 |     64 |\n| Red    |   3.9 |     51 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n|--------+-------+--------|\n| Red    |   1.1 |     58 |\n| Yellow |   3.4 |     51 |\n\nAre original hlines given back?\n#+BEGIN: aggregate :table \"withhline\" :cols \"cölØr vâluε 'ra;han'\" :hline 1\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Yellow |   9.1 |     95 |\n| Red    |   2.6 |     84 |\n|--------+-------+--------|\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Yellow |   5.4 |     17 |\n|--------+-------+--------|\n| Blue   |   4.9 |     64 |\n| Red    |   3.9 |     51 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n|--------+-------+--------|\n| Red    |   1.1 |     58 |\n| Yellow |   3.4 |     51 |\n#+END:\n\nI do not specify hlines in the output\n#+BEGIN: aggregate :table \"withhline\" :cols \"cölØr vâluε 'ra;han'\"\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Yellow |   9.1 |     95 |\n| Red    |   2.6 |     84 |\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Yellow |   5.4 |     17 |\n| Blue   |   4.9 |     64 |\n| Red    |   3.9 |     51 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n| Red    |   1.1 |     58 |\n| Yellow |   3.4 |     51 |\n#+END:\n\nWhat if I want hline on cölØr?\n#+BEGIN: aggregate :table \"withhline\" :cols \"cölØr;^a vâluε 'ra;han'\" :hline 1\n| cölØr  | vâluε | ra;han |\n|--------+-------+--------|\n| Blue   |   8.7 |     52 |\n| Blue   |   7.0 |     29 |\n| Blue   |   4.9 |     64 |\n|--------+-------+--------|\n| Red    |   1.3 |     41 |\n| Red    |   3.5 |     35 |\n| Red    |   2.6 |     84 |\n| Red    |   3.9 |     51 |\n| Red    |   1.1 |     58 |\n|--------+-------+--------|\n| Yellow |   9.1 |     95 |\n| Yellow |   5.4 |     17 |\n| Yellow |   2.4 |     55 |\n| Yellow |   6.6 |     34 |\n| Yellow |   3.4 |     51 |\n#+END:\n\nAnd if I explicitly require hline column?\n#+BEGIN: aggregate :table \"withhline\" :cols \"hline;^n cölØr;^a vâluε 'ra;han'\"\n| hline | cölØr  | vâluε | ra;han |\n|-------+--------+-------+--------|\n|     0 | Red    |   1.3 |     41 |\n|     0 | Red    |   3.5 |     35 |\n|     0 | Red    |   2.6 |     84 |\n|     0 | Yellow |   9.1 |     95 |\n|     1 | Blue   |   8.7 |     52 |\n|     1 | Blue   |   7.0 |     29 |\n|     1 | Yellow |   5.4 |     17 |\n|     2 | Blue   |   4.9 |     64 |\n|     2 | Red    |   3.9 |     51 |\n|     2 | Yellow |   2.4 |     55 |\n|     2 | Yellow |   6.6 |     34 |\n|     3 | Red    |   1.1 |     58 |\n|     3 | Yellow |   3.4 |     51 |\n#+END:\n\nAnd hline rows as well as column?\n#+BEGIN: aggregate :table \"withhline\" :cols \"hline;^N cölØr;^a vâluε 'ra;han'\" :hline 1\n| hline | cölØr  | vâluε | ra;han |\n|-------+--------+-------+--------|\n|     3 | Red    |   1.1 |     58 |\n|     3 | Yellow |   3.4 |     51 |\n|-------+--------+-------+--------|\n|     2 | Blue   |   4.9 |     64 |\n|     2 | Red    |   3.9 |     51 |\n|     2 | Yellow |   2.4 |     55 |\n|     2 | Yellow |   6.6 |     34 |\n|-------+--------+-------+--------|\n|     1 | Blue   |   8.7 |     52 |\n|     1 | Blue   |   7.0 |     29 |\n|     1 | Yellow |   5.4 |     17 |\n|-------+--------+-------+--------|\n|     0 | Red    |   1.3 |     41 |\n|     0 | Red    |   3.5 |     35 |\n|     0 | Red    |   2.6 |     84 |\n|     0 | Yellow |   9.1 |     95 |\n#+END:\n\nSame with hline & cölØr to separate blocks\n#+BEGIN: aggregate :table \"withhline\" :cols \"hline;^N cölØr;^a vâluε 'ra;han'\" :hline 2\n| hline | cölØr  | vâluε | ra;han |\n|-------+--------+-------+--------|\n|     3 | Red    |   1.1 |     58 |\n|-------+--------+-------+--------|\n|     3 | Yellow |   3.4 |     51 |\n|-------+--------+-------+--------|\n|     2 | Blue   |   4.9 |     64 |\n|-------+--------+-------+--------|\n|     2 | Red    |   3.9 |     51 |\n|-------+--------+-------+--------|\n|     2 | Yellow |   2.4 |     55 |\n|     2 | Yellow |   6.6 |     34 |\n|-------+--------+-------+--------|\n|     1 | Blue   |   8.7 |     52 |\n|     1 | Blue   |   7.0 |     29 |\n|-------+--------+-------+--------|\n|     1 | Yellow |   5.4 |     17 |\n|-------+--------+-------+--------|\n|     0 | Red    |   1.3 |     41 |\n|     0 | Red    |   3.5 |     35 |\n|     0 | Red    |   2.6 |     84 |\n|-------+--------+-------+--------|\n|     0 | Yellow |   9.1 |     95 |\n#+END:\n\n* Test filter only\n#+name: planet\n| planet  |   mass kg | dist MKM |\n|---------+-----------+----------|\n| Sun     | 1.9891e30 |        0 |\n| Mercury | 3.3022e23 |       60 |\n| Venus   | 4.8685e24 |      100 |\n| Earth   | 5.9736e24 |      150 |\n| Mars    | 6.4185e23 |      220 |\n| Jupiter | 1.8986e27 |      780 |\n| Saturn  | 5.6846e26 |     1420 |\n| Uranus  | 8.6810e25 |     2870 |\n| Neptune | 10.243e25 |     4500 |\n| Pluto   |   1.25e22 |     5800 |\n\nWithout :cols parameter, we get all columns\n\n#+BEGIN: aggregate :table \"planet\" :cond (> (string-to-number \"dist MKM\") 150)\n| planet  |   mass kg | dist MKM |\n|---------+-----------+----------|\n| Mars    | 6.4185e23 |      220 |\n| Jupiter | 1.8986e27 |      780 |\n| Saturn  | 5.6846e26 |     1420 |\n| Uranus  | 8.6810e25 |     2870 |\n| Neptune | 10.243e25 |     4500 |\n| Pluto   |   1.25e22 |     5800 |\n#+END:\n\nWhat happens without column names in the input?\n\n#+name: planetnh\n| Sun     | 1.9891e30 |        0 |\n| Mercury | 3.3022e23 |       60 |\n| Venus   | 4.8685e24 |      100 |\n| Earth   | 5.9736e24 |      150 |\n| Mars    | 6.4185e23 |      220 |\n| Jupiter | 1.8986e27 |      780 |\n| Saturn  | 5.6846e26 |     1420 |\n| Uranus  | 8.6810e25 |     2870 |\n| Neptune | 10.243e25 |     4500 |\n| Pluto   |   1.25e22 |     5800 |\n\n#+BEGIN: aggregate :table \"planetnh\" :cond (<= (string-to-number \"$3\") 150)\n| $1      |        $2 |  $3 |\n|---------+-----------+-----|\n| Sun     | 1.9891e30 |   0 |\n| Mercury | 3.3022e23 |  60 |\n| Venus   | 4.8685e24 | 100 |\n| Earth   | 5.9736e24 | 150 |\n#+END:\n\n* Test custom column names\n\n#+BEGIN: aggregate :table \"pulledtable\" :cols \"pé;^n vsum(xà);'sum_of_xà' vmean(yÿ);'average Ÿ' vmax(zö);'MAX of ZÖ'\"\n| pé | sum_of_xà |     average Ÿ | MAX of ZÖ |\n|----+-----------+---------------+-----------|\n|  1 |      20.1 |             4 |         9 |\n|  2 |        12 |             5 |         5 |\n|  3 |        29 |           5.5 |         9 |\n|  4 |     a + b | a / 2 + b / 2 |         8 |\n|  5 | a + b + 3 | 5 a + b^2 / 2 |         8 |\n#+END:\n\n* Test no collision\nThere should be no collision between column names and reserved Calc function names.\nFor instance ~vsum~, which is a Calc function, should be usable as a column name.\n\n#+name: keyword-collision\n| vmean | sort | vsum | sum | vmax | aaa |\n|-------+------+------+-----+------+-----|\n|     2 | 12.3 |   43 |  43 |    1 | 8.2 |\n|     8 | 34.4 |   81 |  81 |    1 | 9.3 |\n|     4 | 51.5 |   40 |  40 |    1 | 1.3 |\n|     5 |  8.1 |   27 |  27 |    2 | 3.9 |\n|     2 |  4.7 |   41 |  41 |    2 | 3.5 |\n|     9 | 33.9 |   62 |  62 |    3 | 2.1 |\n|     1 | 41.7 |   83 |  83 |    3 | 2.7 |\n\n#+BEGIN: aggregate :table \"keyword-collision\" :cols \"vmax count() vsum(vmean) vsum(sort) sort(vsum) sort(sum) vmean(sum);%.2f vmean(vsum);f2\"\n| vmax | count() | vsum(vmean) | vsum(sort) | sort(vsum)   | sort(sum)    | vmean(sum) | vmean(vsum) |\n|------+---------+-------------+------------+--------------+--------------+------------+-------------|\n|    1 |       3 |          14 |       98.2 | [40, 43, 81] | [40, 43, 81] |      54.67 |       54.67 |\n|    2 |       2 |           7 |       12.8 | [27, 41]     | [27, 41]     |      34.00 |          34 |\n|    3 |       2 |          10 |       75.6 | [62, 83]     | [62, 83]     |      72.50 |       72.50 |\n#+END:\n\n* Test disordered formatters & decorators\n\n#+BEGIN: aggregate :table \"planet\" :cols \"planet vmax('mass kg');^n;e4;'MassKG' vmin('dist MKM')*1e6;^N;'DistKM';e2\"\n| planet  |   MassKG | DistKM |\n|---------+----------+--------|\n| Pluto   |  12.5e21 |  5.8e9 |\n| Mercury | 330.2e21 |   60e6 |\n| Mars    | 641.9e21 |  220e6 |\n| Venus   | 4.869e24 |  100e6 |\n| Earth   | 5.974e24 |  150e6 |\n| Uranus  | 86.81e24 |  2.9e9 |\n| Neptune | 102.4e24 |  4.5e9 |\n| Saturn  | 568.5e24 |  1.4e9 |\n| Jupiter | 1.899e27 |  780e6 |\n| Sun     | 1.989e30 |    0e0 |\n#+END:\n\n* Test lambda post-processing\n\n#+BEGIN: aggregate :table \"pulledtable\" :cols \"qû vsum(zö)\" :post (lambda (table) (append table '(hline (c 112233))))\n| qû | vsum(zö) |\n|----+----------|\n| b  |       38 |\n| a  |       29 |\n|----+----------|\n| c  |   112233 |\n#+END:\n\n#+BEGIN: aggregate :table \"pulledtable\" :cols \"qû vsum(zö)\" :post (lambda (table) (append '((c 112233) hline) table))\n| c  |   112233 |\n|----+----------|\n| qû | vsum(zö) |\n|----+----------|\n| b  |       38 |\n| a  |       29 |\n#+END:\n\n* Test babel post-processing\n\n#+BEGIN: aggregate :table \"pulledtable\" :cols \"qû vsum(zö)\" :post \"post-proc-babel(*this*)\"\n| AA |     BB |\n|----+--------|\n| c  | 112233 |\n|----+--------|\n| b  |     38 |\n| a  |     29 |\n#+END:\n\n#+name: post-proc-babel\n#+begin_src elisp :var intbl=\"\" :colnames '(AA BB)\n(append\n '((c 112233) hline)\n intbl))\n#+end_src\n\n* Test push lambda post-processing\n\n#+ORGTBL: SEND sent-aggregate-post orgtbl-to-aggregated-table :cols \"a vsum(b) vsum(c)\" :post (lambda (tbl) (append tbl '(hline (h 9 \"8\"))))\n#+ORGTBL: SEND sent-transpose-post orgtbl-to-transposed-table :cols \"a c\"               :post (lambda (tbl) (append tbl '(hline (h \"\" 3.4 \"8.8\"))))\n| a |  b |  c |\n|---+----+----|\n| x | 34 | 56 |\n| i | 90 | 12 |\n| x | 51 |  3 |\n| i |  1 | 11 |\n\n#+BEGIN RECEIVE ORGTBL sent-aggregate-post\n| a | vsum(b) | vsum(c) |\n|---+---------+---------|\n| x |      85 |      59 |\n| i |      91 |      23 |\n|---+---------+---------|\n| h |       9 |       8 |\n#+END RECEIVE ORGTBL sent-aggregate-post\n\n#+BEGIN RECEIVE ORGTBL sent-transpose-post\n| a |   |   x |   i | x |  i |\n| c |   |  56 |  12 | 3 | 11 |\n|---+---+-----+-----+---+----|\n| h |   | 3.4 | 8.8 |\n#+END RECEIVE ORGTBL sent-transpose-post\n\n* Test push babel post-processing\n\n#+ORGTBL: SEND sent-transpose-post-babel orgtbl-to-transposed-table :cols \"p r q\"             :post \"post-proc-babel-send(*this*)\"\n#+ORGTBL: SEND sent-aggregate-post-babel orgtbl-to-aggregated-table :cols \"p vsum(q) vsum(r)\" :post \"post-proc-babel-send(intbl=*this*)\"\n|     q | p |     r |\n|-------+---+-------|\n|  34.9 | x |  56.1 |\n|  9.20 | i |  77.2 |\n| 51.29 | x |  3.86 |\n|  76.7 | i | 19.47 |\n\n#+BEGIN RECEIVE ORGTBL sent-aggregate-post-babel\n| x   |  86.19 | 59.96 |\n| i   |   85.9 | 96.67 |\n| add | 3.1416 |   5.6 |\n|-----+--------+-------|\n| sub |  2.718 | -42.0 |\n#+END RECEIVE ORGTBL sent-aggregate-post-babel\n\n#+BEGIN RECEIVE ORGTBL sent-transpose-post-babel\n| p   |        |     x |    i |     x |     i |\n| r   |        |  56.1 | 77.2 |  3.86 | 19.47 |\n| q   |        |  34.9 | 9.20 | 51.29 |  76.7 |\n| add | 3.1416 |   5.6 |\n|-----+--------+-------+------+-------+-------|\n| sub |  2.718 | -42.0 |\n#+END RECEIVE ORGTBL sent-transpose-post-babel\n\n#+name: post-proc-babel-send\n#+begin_src elisp :var intbl=\"\"\n(append\n intbl\n '((add 3.1416 \"5.6\") hline (sub 2.718 \"-42.0\")))\n#+end_src\n\n* Japanese characters\nJapanese characters are wider than ASCII ones.\nIn mono-spaced fonts, they are often 2 times wider.\n\nNot all fonts are equal. The Ubuntu one is not too bad, although not perfect:\n: (set-face-font 'default \"Ubuntu Mono\")\n\n#+name: 日本のテーブル\n| 如何         | 量 |\n|--------------+----|\n| 急行電車     | 23 |\n| 山に雪が降る | 21 |\n| 鳥と花       | 34 |\n| 急行電車     | 61 |\n| 鳥と花       | 93 |\n| 山に雪が降る | 48 |\n\n#+BEGIN: aggregate :table \"日本のテーブル\" :cols \"如何 vsum(量)\"\n| 如何         | vsum(量) |\n|--------------+----------|\n| 急行電車     |       84 |\n| 山に雪が降る |       69 |\n| 鳥と花       |      127 |\n#+END:\n\n* Alignment cookies\nWhat to do with cookies?\n<> <l> <c> <r> <12> <l12> <c12> <r12>\n\nThey are not real data, rather metadata. Mixed into data, they may\nresult in false aggregations. Therefore they should be ignored.\n\nBut in the header of tables, cookies do not change aggregated\nresults. They format the source column. Probably the aggregated column\nmay benefit from the same formatting. Therefore, cookies are kept in\nheaders.\n\n#+name: with-cookies\n| color  | quantity |  level |\n| <l>    |     <r7> |    <3> |\n| kolor  |     kiom | nivelo |\n|--------+----------+--------|\n| yellow |       72 |      3 |\n| green  |       55 |      0 |\n| <c>    |          |      4 |\n| orange |       80 |      0 |\n| white  |       19 |      6 |\n| green  |        4 |      4 |\n| yellow |       58 |      5 |\n|        |     <25> |      0 |\n| orange |       22 |      4 |\n| orange |        7 |      4 |\n| <>     |       <> |      2 |\n| red    |       71 |      3 |\n| blue   |       56 |      3 |\n| red    |       52 |      5 |\n| <7>    |          |      3 |\n| orange |       35 |      0 |\n|        |      <r> |      3 |\n| yellow |       23 |      0 |\n| <l>    |     <44> |      0 |\n| blue   |       93 |      4 |\n| black  |      <l> |      0 |\n| green  |       82 |      2 |\n| <9>    |      <4> |      5 |\n\n#+BEGIN: aggregate :table \"with-cookies\" :cols \"color vsum(quantity);'sum' count();'nb' vsum(quantity)/vmean(level);'leveled'\"\n| color  |  sum | nb |       leveled |\n| <l>    | <r7> |    |               |\n| kolor  | kiom |    |               |\n|--------+------+----+---------------|\n| yellow |  153 |  3 | 57.3749999999 |\n| green  |  141 |  3 |          70.5 |\n| orange |  144 |  4 |            72 |\n| white  |   19 |  1 | 3.16666666667 |\n| red    |  123 |  2 |         30.75 |\n| blue   |  149 |  2 | 42.5714285714 |\n#+END:\n\n#+BEGIN: transpose :table \"with-cookies\"\n| color    | <l>  | kolor  |   | yellow | green | orange | white | green | yellow | orange | orange | red | blue | red | orange | yellow | blue | green |\n| quantity | <r7> | kiom   |   |     72 |    55 |     80 |    19 |     4 |     58 |     22 |      7 |  71 |   56 |  52 |     35 |     23 |   93 |    82 |\n| level    | <3>  | nivelo |   |      3 |     0 |      0 |     6 |     4 |      5 |      4 |      4 |   3 |    3 |   5 |      0 |      0 |    4 |     2 |\n#+END:\n\n* 1st data row is not the header\nWhen the input table does not have a header,\nthe first data row should not be mistaken with a header.\n\n#+name: missing-header\n| a | 12 | 33 |\n| c | 13 | 12 |\n| x | 14 | 12 |\n| y | 15 | 45 |\n| z |  7 |  7 |\n\n#+BEGIN: aggregate :table \"missing-header\" :cols \"$1\" :cond (equal $3 \"12\")\n| $1 |\n|----|\n| c  |\n| x  |\n#+END:\n\nIn case of a mistake, the result is:\n| $1 |\n|----|\n| z  |\n\n* Debug settings\n\n4 settings: c q C Q\n\n#+BEGIN: aggregate :table \"withhline\" :cols \"hline vsum(vâluε);c vsum(vâluε);q vsum(vâluε);C vsum(vâluε);Q\"\n| hline | vsum(vâluε) | vsum(vâluε)                       | vsum(vâluε)                | vsum(vâluε)                                                                   |\n|-------+-------------+-----------------------------------+----------------------------+-------------------------------------------------------------------------------|\n|     0 | vsum($2)    | (calcFunc-vsum (calcFunc-Frux 2)) | vsum([1.3, 3.5, 9.1, 2.6]) | (calcFunc-vsum (vec (float 13 -1) (float 35 -1) (float 91 -1) (float 26 -1))) |\n|     1 | vsum($2)    | (calcFunc-vsum (calcFunc-Frux 2)) | vsum([8.7, 7., 5.4])       | (calcFunc-vsum (vec (float 87 -1) (float 7 0) (float 54 -1)))                 |\n|     2 | vsum($2)    | (calcFunc-vsum (calcFunc-Frux 2)) | vsum([4.9, 3.9, 2.4, 6.6]) | (calcFunc-vsum (vec (float 49 -1) (float 39 -1) (float 24 -1) (float 66 -1))) |\n|     3 | vsum($2)    | (calcFunc-vsum (calcFunc-Frux 2)) | vsum([1.1, 3.4])           | (calcFunc-vsum (vec (float 11 -1) (float 34 -1)))                             |\n#+END:\n\n* hline as a column to be aggregated\nDoes it make sense to calculate something based on hline?\nAnyway, it is now available at no cost.\n\n#+BEGIN: aggregate :table \"withhline\" :cols \"cölØr vmean(hline*15) vlist(hline)\"\n| cölØr  | vmean(hline*15) | vlist(hline)  |\n|--------+-----------------+---------------|\n| Red    |              15 | 0, 0, 0, 2, 3 |\n| Yellow |              24 | 0, 1, 2, 2, 3 |\n| Blue   |              20 | 1, 1, 2       |\n#+END:\n\nWe see that Reds are more at the begining of the input table,\nwhile Yellows are more at the end.\n\n* Input table is a Babel script\n\nNote the =:colnames yes= parameter to output a header with label & value\ncolumn names.\n\nThe table resulting from the =ascript= script is computed on the fly, it\nappears nowhere in the buffer.\n\n#+name: ascript\n#+begin_src elisp :colnames yes\n`(\n  (label \"value\")                       ; cells are symbols or strings\n  hline\n  ,@(cl-loop\n     for i from 1 to 20\n     collect\n     (list\n      (format \"%c\" (+ ?a (% i 3)))      ; cell is a string\n      i)))                              ; cell is a number\n#+end_src\n\nUse column names in the =:cols= specification:\n\n#+BEGIN: aggregate :table \"ascript\" :cols \"label vsum(value)\"\n| label | vsum(value) |\n|-------+-------------|\n| b     |          70 |\n| c     |          77 |\n| a     |          63 |\n#+END:\n\nUse dollar to specify columns in =:cols=:\n\n#+BEGIN: aggregate :table \"ascript\" :cols \"$1 vsum($2)\"\n| $1 | vsum($2) |\n|----+----------|\n| b  |       70 |\n| c  |       77 |\n| a  |       63 |\n#+END:\n\na script with a parameter:\n\n#+name: ascriptparam\n#+begin_src elisp :colnames yes :var len=20\n`(\n  (\"label\" value)                       ; cells are symbols or strings\n  hline\n  ,@(cl-loop\n     for i from 1 to len\n     collect\n     (list\n      (format \"%c\" (+ ?a (% i 3)))      ; cell is a string\n      i)))                              ; cell is a number\n#+end_src\n\n#+BEGIN: aggregate :table \"ascriptparam(len=10)\" :cols \"label vsum(value)\"\n| label | vsum(value) |\n|-------+-------------|\n| b     |          22 |\n| c     |          15 |\n| a     |          18 |\n#+END:\n\na longer table is generated (100 rows),\nbut only 10 rows are retained (12 = 10 rows + header + hline)\n\n#+BEGIN: aggregate :table \"ascriptparam(len=100)[0:12]\" :cols \"label vsum(value)\"\n| label | vsum(value) |\n|-------+-------------|\n| b     |          22 |\n| c     |          26 |\n| a     |          18 |\n#+END:\n\n* Column & single-cell formulas\nIs the Org Mode bug overcome?\n\nIt happens when\n- there are both a column formula and a single cell formula\n- they need to add new columns\n\n#+BEGIN: aggregate :table \"planet\" :cols \"planet vsum('dist MKM');'km'\"\n| planet  |   km |    au |\n|---------+------+-------|\n| Sun     |    0 |  0.00 |\n| Mercury |   60 |  0.40 |\n| Venus   |  100 |  0.67 |\n| Earth   |  150 |  1.00 |\n| Mars    |  220 |  1.47 |\n| Jupiter |  780 |  5.20 |\n| Saturn  | 1420 |  9.47 |\n| Uranus  | 2870 | 19.13 |\n| Neptune | 4500 | 30.00 |\n| Pluto   | 5800 | 38.67 |\n#+TBLFM: $3=$2/150;%.2f::@1$3=au\n#+END:\n\n* Aggregate on computed bins\nSometimes, input columns are not enough to aggregate on.\nVirtual computed columns may be handy.\n\n#+name: want-month\n| Date             | Quty |\n|------------------+------|\n| [2027-02-10 mer] |   38 |\n| [2027-02-21 dim] |   58 |\n| [2027-03-04 ĵaŭ] |   52 |\n| [2027-03-15 lun] |   35 |\n| [2027-03-26 ven] |   62 |\n| [2027-04-06 mar] |   19 |\n| [2027-04-17 sab] |   22 |\n| [2027-04-28 mer] |   63 |\n| [2027-05-09 dim] |   70 |\n| [2027-05-20 ĵaŭ] |   51 |\n| [2027-05-31 lun] |   55 |\n| [2027-06-11 ven] |   49 |\n| [2027-06-22 mar] |   96 |\n| [2027-07-03 sab] |   62 |\n| [2027-07-14 mer] |    7 |\n| [2027-07-25 dim] |   43 |\n\nAggregate on months extracted from 'Date':\n\n#+BEGIN: aggregate :table \"want-month\" :cols \"Month vsum(Quty)\" :precompute (\"month(Date);'Month'\")\n| Month | vsum(Quty) |\n|-------+------------|\n|     2 |         96 |\n|     3 |        149 |\n|     4 |        104 |\n|     5 |        176 |\n|     6 |        145 |\n|     7 |        112 |\n#+END:\n\nAn input table with the first column acting as a tag, and the second as a quantity:\n\n#+name: want-bins\n| 129 | 32.56 |\n| 133 | 71.45 |\n| 139 | 72.80 |\n| 172 | 14.99 |\n| 343 | 88.58 |\n| 373 | 51.56 |\n| 406 | 87.66 |\n| 444 | 14.13 |\n| 459 | 52.87 |\n| 510 | 59.10 |\n| 527 | 78.41 |\n| 634 | 23.71 |\n| 673 | 91.14 |\n| 739 | 83.03 |\n| 750 | 28.13 |\n| 757 | 60.63 |\n| 792 | 64.62 |\n| 833 | 13.28 |\n| 848 | 29.89 |\n| 871 | 82.70 |\n| 945 | 22.95 |\n| 967 | 42.58 |\n\nWe want to aggregate on coarse bins made of hundredths of the firt column:\n\n#+BEGIN: aggregate :table \"want-bins\" :cols \"$3 vmean($2);f2\" :precompute (\"'(floor (/ (string-to-number $1) 100))\")\n| $3 | vmean($2) |\n|----+-----------|\n|  1 |     47.95 |\n|  3 |     70.07 |\n|  4 |     51.55 |\n|  5 |     68.76 |\n|  6 |     57.43 |\n|  7 |     59.10 |\n|  8 |     41.96 |\n|  9 |     32.77 |\n#+END:\n\nSame aggregation, with additional metrics (min & max):\n\n#+BEGIN: aggregate :table \"want-bins\" :cols \"$3 vmin(H) vmax(H) vmean($2);f2\" :precompute \"'(floor (/ (string-to-number $1) 100)) :: $1/100;'H'\"\n| $3 | vmin(H) | vmax(H) | vmean($2) |\n|----+---------+---------+-----------|\n|  1 |    1.29 |    1.72 |     47.95 |\n|  3 |    3.43 |    3.73 |     70.07 |\n|  4 |    4.06 |    4.59 |     51.55 |\n|  5 |     5.1 |    5.27 |     68.76 |\n|  6 |    6.34 |    6.73 |     57.43 |\n|  7 |    7.39 |    7.92 |     59.10 |\n|  8 |    8.33 |    8.71 |     41.96 |\n|  9 |    9.45 |    9.67 |     32.77 |\n#+END:\n\nLet us format a pre-computed column:\n\n#+BEGIN: aggregate :table \"want-bins\" :cols \"LL list(H)\" :precompute \"'(floor (/ (string-to-number $1) 100));%.3f;'LL' :: $1/100;'H';%.1f\"\n|    LL | list(H)            |\n|-------+--------------------|\n| 1.000 | 1.3, 1.3, 1.4, 1.7 |\n| 3.000 | 3.4, 3.7           |\n| 4.000 | 4.1, 4.4, 4.6      |\n| 5.000 | 5.1, 5.3           |\n| 6.000 | 6.3, 6.7           |\n| 7.000 | 7.4, 7.5, 7.6, 7.9 |\n| 8.000 | 8.3, 8.5, 8.7      |\n| 9.000 | 9.4, 9.7           |\n#+END:\n\n* Distant tables\n\ntable by name\n\n#+BEGIN: aggregate :table \"distant-tests.org:distanttable\" :cols \"tag vsum(val)\"\n| tag | vsum(val) |\n|-----+-----------|\n| A   |    405.43 |\n| BB  |    453.63 |\n| CCC |    215.71 |\n#+END:\n\nBabel by name\n\n#+BEGIN: aggregate :table \"distant-tests.org:distantbabel\" :cols \"tag vsum(value)\"\n| tag | vsum(value) |\n|-----+-------------|\n| BB  |         825 |\n| CCC |         818 |\n| A   |         832 |\n#+END:\n\nBabel by name and parameter\n\n#+BEGIN: aggregate :table \"distant-tests.org:distantbabel(factor=17)\" :cols \"tag vsum(value)\"\n| tag | vsum(value) |\n|-----+-------------|\n| BB  |         825 |\n| CCC |        1114 |\n| A   |         536 |\n#+END:\n\nBabel, mean of floating points\n\n#+BEGIN: aggregate :table \"distant-tests.org:distantbabel(factor=17)\" :cols \"tag vmean(inv) count()\"\n| tag |     vmean(inv) | count() |\n|-----+----------------+---------|\n| BB  | 0.976763739953 |      17 |\n| CCC | 0.975810407486 |      17 |\n| A   | 0.976263808395 |      16 |\n#+END:\n\ntable by ID\n\n#+BEGIN: aggregate :table \"55ab27a2-c44b-4a14-9ba4-f6879375207d[0:7]\" :cols \"ref vsum(val)\"\n| ref  | vsum(val) |\n|------+-----------|\n| S    |     685.6 |\n| TT   |     996.4 |\n| UUU  |     945.2 |\n| VVVV |     974.7 |\n#+END:\n\n* CSV table\n\nabundance of large cities per country\nthe source CSV file is TAB-separated, which is guessed\n\n#+BEGIN: aggregate :table \"geography-a.csv:(csv)\" :cols \"$2 count();^N vsum($3)\"\n| $2             | count() |  vsum($3) |\n|----------------+---------+-----------|\n| China          |      28 | 264183191 |\n| India          |      10 | 140092165 |\n| Japan          |       4 |  71117892 |\n| Brazil         |       4 |  48284586 |\n| Mexico         |       3 |  33697146 |\n| Bangladesh     |       2 |  30197563 |\n| Egypt          |       2 |  28897566 |\n| Pakistan       |       2 |  32895483 |\n| Nigeria        |       2 |  21763797 |\n| Turkey         |       2 |  21777496 |\n| Russia         |       2 |  18346030 |\n| Vietnam        |       2 |  15392473 |\n| Saudi Arabia   |       2 |  12980333 |\n| Spain          |       2 |  12577695 |\n| South Africa   |       2 |  11501203 |\n| Australia      |       2 |  10663074 |\n| DR Congo       |       1 |  17815364 |\n| Argentina      |       1 |  15714124 |\n| Philippines    |       1 |  15211511 |\n| Colombia       |       1 |  11779275 |\n| Indonesia      |       1 |  11628728 |\n| Peru           |       1 |  11529982 |\n| Thailand       |       1 |  11415533 |\n| France         |       1 |  11352823 |\n| Angola         |       1 |  10049628 |\n| South Korea    |       1 |  10059272 |\n| United Kingdom |       1 |   9818142 |\n| Iran           |       1 |   9738111 |\n| Malaysia       |       1 |   8980578 |\n| Tanzania       |       1 |   8529744 |\n| Iraq           |       1 |   8154140 |\n| United States  |       1 |   7966324 |\n| Hong Kong      |       1 |   7791531 |\n| Chile          |       1 |   6973392 |\n| Sudan          |       1 |   6778168 |\n| Canada         |       1 |   6513813 |\n| Singapore      |       1 |   6167759 |\n| Ivory Coast    |       1 |   6054358 |\n| Ethiopia       |       1 |   5961711 |\n| Myanmar        |       1 |   5829964 |\n| Kenya          |       1 |   5772121 |\n| Afghanistan    |       1 |   4862586 |\n| Cameroon       |       1 |   4859198 |\n| Israel         |       1 |   4577871 |\n| Taiwan         |       1 |   4570576 |\n#+END:\n\nA CVS table oddly formatted, with a header:\n\n#+BEGIN: aggregate :table \"hline.csv:(csv header)\" :cols \"cc vsum(bb) vmean(aa)\"\n| cc    | vsum(bb) | vmean(aa) |\n|-------+----------+-----------|\n| apple |      831 |      62.2 |\n| grape |      -22 |     13.75 |\n#+END:\n\nHere we add an external header, even though there is already a header\nin the CSV file, and see what happens:\n\n#+BEGIN: aggregate :table \"hline.csv:(csv colnames (level quantity fruit))\" :cols \"fruit vsum(quantity)\"\n| fruit | vsum(quantity) |\n|-------+----------------|\n| cc    |             bb |\n| apple |            831 |\n| grape |            -22 |\n#+END:\n\nCheck if horizontal separators are recognized:\n\n#+BEGIN: aggregate :table \"hline.csv:(csv header)\" :cols \"hline cc vsum(bb)\" :hline 1\n| hline | cc    | vsum(bb) |\n|-------+-------+----------|\n|     0 | apple |      654 |\n|     0 | grape |      -78 |\n|-------+-------+----------|\n|     1 | grape |       45 |\n|     1 | apple |      192 |\n|-------+-------+----------|\n|     2 | apple |      -15 |\n|     2 | grape |       11 |\n#+END:\n\nCSV in distant Org file\n\n#+BEGIN: aggregate :table \"distant-tests.org:distantcsv(csv header)\" :cols \"label vsum(quantity)\"\n| label | vsum(quantity) |\n|-------+----------------|\n| jes   |             70 |\n| ne    |           -106 |\n#+END:\n\n* JSON table\n\nsame as in CSV\n\n#+BEGIN: aggregate :table \"geography-a.json:(json)\" :cols \"$2 count();^N vsum($3)\"\n| $2             | count() |  vsum($3) |\n|----------------+---------+-----------|\n| China          |      28 | 264183191 |\n| India          |      10 | 140092165 |\n| Japan          |       4 |  71117892 |\n| Brazil         |       4 |  48284586 |\n| Mexico         |       3 |  33697146 |\n| Bangladesh     |       2 |  30197563 |\n| Egypt          |       2 |  28897566 |\n| Pakistan       |       2 |  32895483 |\n| Nigeria        |       2 |  21763797 |\n| Turkey         |       2 |  21777496 |\n| Russia         |       2 |  18346030 |\n| Vietnam        |       2 |  15392473 |\n| Saudi Arabia   |       2 |  12980333 |\n| Spain          |       2 |  12577695 |\n| South Africa   |       2 |  11501203 |\n| Australia      |       2 |  10663074 |\n| DR Congo       |       1 |  17815364 |\n| Argentina      |       1 |  15714124 |\n| Philippines    |       1 |  15211511 |\n| Colombia       |       1 |  11779275 |\n| Indonesia      |       1 |  11628728 |\n| Peru           |       1 |  11529982 |\n| Thailand       |       1 |  11415533 |\n| France         |       1 |  11352823 |\n| Angola         |       1 |  10049628 |\n| South Korea    |       1 |  10059272 |\n| United Kingdom |       1 |   9818142 |\n| Iran           |       1 |   9738111 |\n| Malaysia       |       1 |   8980578 |\n| Tanzania       |       1 |   8529744 |\n| Iraq           |       1 |   8154140 |\n| United States  |       1 |   7966324 |\n| Hong Kong      |       1 |   7791531 |\n| Chile          |       1 |   6973392 |\n| Sudan          |       1 |   6778168 |\n| Canada         |       1 |   6513813 |\n| Singapore      |       1 |   6167759 |\n| Ivory Coast    |       1 |   6054358 |\n| Ethiopia       |       1 |   5961711 |\n| Myanmar        |       1 |   5829964 |\n| Kenya          |       1 |   5772121 |\n| Afghanistan    |       1 |   4862586 |\n| Cameroon       |       1 |   4859198 |\n| Israel         |       1 |   4577871 |\n| Taiwan         |       1 |   4570576 |\n#+END:\n\nSame as CSV, vector of vectors, first vector is the header\n\n#+BEGIN: aggregate :table \"hline-header.json:(json header)\" :cols \"cc vsum(bb) vmean(aa)\"\n| cc    | vsum(bb) | vmean(aa) |\n|-------+----------+-----------|\n| apple |      831 |      62.2 |\n| grape |      -22 |     13.75 |\n#+END:\n\nSame test, input is mixed vectors and hashed-objects,\nresulting header is a mixture of first vector and keys of hashed-objects.\n\n#+BEGIN: aggregate :table \"hline-hash.json:(json header)\" :cols \"cc vsum(bb) vmean(aa)\"\n| cc    | vsum(bb) | vmean(aa) |\n|-------+----------+-----------|\n| apple |      831 |      62.2 |\n| grape |      -22 |     13.75 |\n#+END:\n\nUnderstand input hline\n\n#+BEGIN: aggregate :table \"hline-hash.json:(json)\" :cols \"hline bb\" :hline \"1\"\n| hline |  bb |\n|-------+-----|\n|     0 | 654 |\n|     0 | -78 |\n|-------+-----|\n|     1 | +45 |\n|     1 | +35 |\n|     1 | +66 |\n|     1 | +91 |\n|-------+-----|\n|     2 | -15 |\n|     2 |   7 |\n|     2 |   4 |\n#+END:\n\nJSON in distant Org file\n\n#+BEGIN: aggregate :table \"distant-tests.org:distantjson(json)\" :cols \"$2 vsum($1)\"\n| $2   | vsum($1) |\n|------+----------|\n| univ |       42 |\n| jes  |      555 |\n| ne   |     -176 |\n#+END:\n"
  },
  {
    "path": "tests/wizard-test.el",
    "content": ";;; wizard-test.el --- Create unit tests for OrgAggregate wizard  -*- coding:utf-8; lexical-binding: t;-*-\n;; Copyright (C) 2013-2026  Thierry Banel\n;; \n;; org-aggregate is free software: you can redistribute it and/or modify\n;; it under the terms of the GNU General Public License as published by\n;; the Free Software Foundation, either version 3 of the License, or\n;; (at your option) any later version.\n;; \n;; org-aggregate is distributed in the hope that it will be useful,\n;; but WITHOUT ANY WARRANTY; without even the implied warranty of\n;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n;; GNU General Public License for more details.\n;; \n;; You should have received a copy of the GNU General Public License\n;; along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n;;; Commentary:\n;; A Lisp function to record a wizard unit test.\n;; - put the cursor somewhere in an Org Mode file,\n;; - call M-x orgtbl-aggregate-bench-create\n;; - call the wizard with\n;;   C-c C-x x aggregate\n;; - use the wizard as usual, your key-strokes are recorded while you type,\n;; - when done, hit the & key,\n;;   this insert a keyboard macro in the Org Mode file,\n;; - the keyboard macro can be executed anytime with C-x e\n;;   to replicate your actions.\n\n(defun orgtbl-aggregate-bench-create ()\n  \"Interactively create a bench.\nWhen done, type &.\"\n  (interactive)\n  (local-set-key \"&\" 'orgtbl-aggregate-bench-collect)\n  (message \"use the wizard, then type & when done\")\n  (kmacro-start-macro nil))\n\n(defun orgtbl-aggregate-bench-collect ()\n  \"Called when typing & to close the interactive wizard session.\nDo not call it directly.\"\n  (interactive)\n  (kmacro-end-macro 1)\n  (insert \"(execute-kbd-macro (kbd \\\"\\n\")\n  (insert (key-description (kmacro--keys (kmacro last-kbd-macro))))\n  (insert \"\\\"))\\n\"))\n"
  },
  {
    "path": "tests/wizardtests.org",
    "content": "# -*- coding:utf-8; -*-\n#+TITLE:  Orgtbl Aggregate wizard unit tests\nCopyright (C) 2013-2026  Thierry Banel\n\norg-aggregate is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\norg-aggregate is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n* How to run?\nRunning all tests should not change anything to this page.\n\nRun this script to complete all the unit tests in a disposable\nbuffer. When done, the buffer and the original, untouched\n~wizardtest.org~, are compared, stopping at the first difference.\n\n#+begin_src elisp :results none\n;; define a utility to run a unit test\n(defun orgtbl-aggregate-wizard-test (&rest args)\n  (execute-kbd-macro\n   (kbd\n    (mapconcat\n     #'identity\n     (cl-loop\n      for a in args\n      if (symbolp a)\n      do (unless (memq a '(:init :isid :orgid :file :name :params :slice\n                                 :precompute :cols :cond :hline :post))\n           (user-error \"tag %s not recognized\" a))\n      else\n      collect a)\n     \"\\n\"))))\n\n;; display this buffer in a window occupying half the frame\n(delete-other-windows)\n(goto-char (point-min))\n(org-cycle '(64))\n(split-window-right)\n\n;; Make a new buffer and fill it with the content of unittests.org\n(let ((f (buffer-file-name)))\n  (switch-to-buffer \"disposable-wizardtests.org\")\n  (erase-buffer)\n  (insert-file f))\n(org-mode)\n(org-cycle '(64))\n\n;; Clean results from prior tests\n(save-excursion\n  (goto-char (point-min))\n  (replace-regexp\n   (rx\n    bol \"#+begin: \" (* not-newline) \"\\n\"\n    (*? (* any) \"\\n\")\n    \"#+end:\" (* any) \"\\n\")\n   \"\"))\n\n;; call all wizard invocations\n(goto-char (point-min))\n(org-next-visible-heading 2)\n;;(while (search-forward \"(execute-kbd-macro\" nil t)\n;;  (beginning-of-line)\n;;  (forward-sexp)\n;;  (forward-line)\n;;  (beginning-of-line)\n;;  (eval-last-sexp nil))\n\n(while (re-search-forward\n        (rx \"orgtbl-aggregate-wizard-test\"\n            (*? (* any) \"\\n\")\n            \"#+end_src\" (* any) \"\\n\")\n        nil t)\n  (org-ctrl-c-ctrl-c))\n\n;; Compare the disposable buffer with the reference wiz1.org\n(goto-char (point-min))\n(compare-windows nil)\n#+end_src\n\n* test1\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init \"C-u C-c C-x x aggr <tab> <return>\"\n :isid \"n\"\n :file \"unitt <tab> <return>\"\n :name \"hlin <tab> <return>\"\n :params \"<return>\"\n :slice \"<return>\"\n :precompute \"y * 1 0 ; ' y 1 0 ' <return>\"\n :cols \"h l i n e SPC f SPC v s u m ( y ) SPC v s u m ( y 1 0 ) <return>\"\n :cond \"<return>\"\n :hline \"<return>\"\n :post \"<return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"unittests.org:hlinetable\" :cols \"hline f vsum(y) vsum(y10)\" :precompute \"y*10;'y10'\"\n| hline | f | vsum(y)        | vsum(y10)             |\n|-------+---+----------------+-----------------------|\n|     0 | 0 | 22             | 220                   |\n|     1 | 1 | 6              | 60                    |\n|     2 | 1 | a + 16         | 10 a + 160            |\n|     3 | 1 | 10 a + b + b^2 | 100 a + 10 b + 10 b^2 |\n#+END:\n\n* a table\n\n#+name: tagqty\n| tag |  val |  qty |\n|-----+------+------|\n| ccc | 9.91 |  4.4 |\n| a   | 5.10 | -4.0 |\n| ccc | 3.86 |  9.9 |\n| bb  | 6.13 |  0.4 |\n| bb  | 8.30 | -6.5 |\n| a   | 4.58 |  8.8 |\n| bb  | 0.68 |  4.6 |\n| a   | 0.96 |  5.6 |\n| ccc | 2.43 |  9.0 |\n| a   | 5.34 | -2.7 |\n| a   | 4.51 | -6.7 |\n| bb  | 3.51 |  5.5 |\n| a   | 9.24 |  3.7 |\n| ccc | 0.19 | -6.7 |\n| ccc | 2.84 |  3.0 |\n| ccc | 3.04 | -5.3 |\n| bb  | 9.78 |  3.1 |\n| ccc | 7.37 | -7.0 |\n| bb  | 0.91 |  4.8 |\n| ccc | 5.28 | -2.4 |\n| bb  | 1.43 |  9.2 |\n| a   | 0.11 |  5.1 |\n\n* test 2\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x a g g <tab> <return>\"\n :isid       \"n\"\n :file       \"<return>\"\n :name       \"t a g q <tab> <return>\"\n :params     \"<return>\"\n :slice      \"[ <kp-0> : <kp-1> <kp-4> ] <return>\"\n :precompute \"<return>\"\n :cols       \"t a g SPC v s u m ( v a l ) SPC v s u m ( q t y ) SPC c o u n t ( ) <return>\"\n :cond       \"( < = SPC ( s t r i n g - t o - n u m b e r SPC v a l ) SPC 9 . 0 0 ) ) <return>\"\n :hline      \"<return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"tagqty[0:14]\" :cols \"tag vsum(val) vsum(qty) count()\" :cond (<= (string-to-number val) 9.0)\n| tag | vsum(val) | vsum(qty) | count() |\n|-----+-----------+-----------+---------|\n| a   |     20.49 |        1. |       5 |\n| ccc |      6.29 |      18.9 |       2 |\n| bb  |     18.62 |        4. |       4 |\n#+END:\n\n* test :post\n\n#+name: addendrow\n#+begin_src elisp :var table=*this* :colnames t\n(message \"TABLE = %S\" table)\n(nconc\n table\n (list\n  'hline\n  (cl-loop\n   for i from 1 to (length (car table))\n   collect (format \"x%d\" i))))\n#+end_src\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x a g g <tab> <return>\"\n :isid       \"n\"\n :file       \"<return>\"\n :name       \"t a g q <tab> <return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :precompute \"<return>\"\n :cols       \"t a g SPC v m e a n ( v a l ) ; f 2 SPC v m e a n ( q t y ) ; f 2 <return>\"\n :cond       \"<return>\"\n :hline      \"<return>\"\n :post       \"a d d e n d r o w <return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"tagqty\" :cols \"tag vmean(val);f2 vmean(qty);f2\" :post \"addendrow\"\n| tag | vmean(val) | vmean(qty) |\n|-----+------------+------------|\n| ccc |       4.37 |       0.61 |\n| a   |       4.26 |       1.40 |\n| bb  |       4.39 |       3.01 |\n|-----+------------+------------|\n| x1  |         x2 |         x3 |\n#+END:\n\n* test :hline\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x a g g <tab> <return>\"\n :isid       \"n\"\n :file       \"<return>\"\n :name       \"t a g q <tab> <return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :precompute \"f l o o r ( v a l ) ; ' v f ' <return>\"\n :cols       \"t a g ; ^ a SPC v f ; ^ n SPC v m e a n ( q t y ) SPC c o u n t ( ) <return>\"\n :cond       \"<return>\"\n :hline      \"1 <return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"tagqty\" :cols \"tag;^a vf;^n vmean(qty) count()\" :hline \"1\" :precompute \"floor(val);'vf'\"\n| tag | vf | vmean(qty) | count() |\n|-----+----+------------+---------|\n| a   |  0 |       5.35 |       2 |\n| a   |  4 |       1.05 |       2 |\n| a   |  5 |      -3.35 |       2 |\n| a   |  9 |        3.7 |       1 |\n|-----+----+------------+---------|\n| bb  |  0 |        4.7 |       2 |\n| bb  |  1 |        9.2 |       1 |\n| bb  |  3 |        5.5 |       1 |\n| bb  |  6 |        0.4 |       1 |\n| bb  |  8 |       -6.5 |       1 |\n| bb  |  9 |        3.1 |       1 |\n|-----+----+------------+---------|\n| ccc |  0 |       -6.7 |       1 |\n| ccc |  2 |         6. |       2 |\n| ccc |  3 |        2.3 |       2 |\n| ccc |  5 |       -2.4 |       1 |\n| ccc |  7 |        -7. |       1 |\n| ccc |  9 |        4.4 |       1 |\n#+END:\n\n* test CSV\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x a g g <tab> <return>\"\n :isid       \"n\"\n :file       \"g e o g r <tab> c <tab> <return>\"\n :name       \"<return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :precompute \"<return>\"\n :cols       \"$ 2 SPC v s u m ( $ 3 ) ; f 0 SPC v m e a n ( $ 4 ) ; f 0 ; ^ n SPC c o u n t ( ) <return>\"\n :cond       \"<return>\"\n :hline      \"<return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"geography-a.csv:(csv)\" :cols \"$2 vsum($3);f0 vmean($4);f0;^n count()\"\n| $2             |  vsum($3) | vmean($4) | count() |\n|----------------+-----------+-----------+---------|\n| Israel         |   4577871 |   4500492 |       1 |\n| Taiwan         |   4570576 |   4522439 |       1 |\n| Cameroon       |   4859198 |   4692347 |       1 |\n| Afghanistan    |   4862586 |   4712793 |       1 |\n| Australia      |  10663074 |  5247013. |       2 |\n| Kenya          |   5772121 |   5560131 |       1 |\n| South Africa   |  11501203 |  5649307. |       2 |\n| Ethiopia       |   5961711 |   5681609 |       1 |\n| Myanmar        |   5829964 |   5706310 |       1 |\n| Ivory Coast    |   6054358 |   5859424 |       1 |\n| Singapore      |   6167759 |   6115882 |       1 |\n| Spain          |  12577695 |   6265703 |       2 |\n| Saudi Arabia   |  12980333 |  6386230. |       2 |\n| Canada         |   6513813 |   6450438 |       1 |\n| Sudan          |   6778168 |   6526345 |       1 |\n| Chile          |   6973392 |   6953542 |       1 |\n| Vietnam        |  15392473 |  7481426. |       2 |\n| Hong Kong      |   7791531 |   7716372 |       1 |\n| Iraq           |   8154140 |   7911328 |       1 |\n| United States  |   7966324 |   8100605 |       1 |\n| Tanzania       |   8529744 |   8188494 |       1 |\n| Malaysia       |   8980578 |   8832827 |       1 |\n| Russia         |  18346030 |   9144834 |       2 |\n| China          | 264183191 |  9260336. |      28 |\n| Iran           |   9738111 |   9606808 |       1 |\n| Angola         |  10049628 |   9648709 |       1 |\n| United Kingdom |   9818142 |   9723207 |       1 |\n| South Korea    |  10059272 |   9991484 |       1 |\n| Nigeria        |  21763797 |  10518043 |       2 |\n| Turkey         |  21777496 |  10765670 |       2 |\n| Mexico         |  33697146 | 11061345. |       3 |\n| Thailand       |  11415533 |  11195528 |       1 |\n| France         |  11352823 |  11304387 |       1 |\n| Peru           |  11529982 |  11388304 |       1 |\n| Indonesia      |  11628728 |  11478423 |       1 |\n| Colombia       |  11779275 |  11660428 |       1 |\n| Brazil         |  48284586 | 11950654. |       4 |\n| India          | 140092165 |  13696246 |      10 |\n| Egypt          |  28897566 | 14165193. |       2 |\n| Bangladesh     |  30197563 | 14745963. |       2 |\n| Philippines    |  15211511 |  14917612 |       1 |\n| Argentina      |  15714124 |  15634092 |       1 |\n| Pakistan       |  32895483 | 15998680. |       2 |\n| DR Congo       |  17815364 |  17025505 |       1 |\n| Japan          |  71117892 | 17814626. |       4 |\n#+END:\n\n* test JSON\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x a g g <tab> <return>\"\n :isid       \"n\"\n :file       \"g e o g r <tab> j <tab> <return>\"\n :name       \"<return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :precompute \"<return>\"\n :cols       \"$ 2 SPC v m i n ( $ 3 ) SPC v m a x ( $ 3 ) ; ^ n <return>\"\n :cond       \"( > = SPC $ 4 SPC 6 0 0 0 0 0 0 ) <return>\"\n :hline      \"<return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"geography-a.json:(json)\" :cols \"$2 vmin($3) vmax($3);^n\" :cond (>= $4 6000000)\n| $2             | vmin($3) | vmax($3) |\n|----------------+----------+----------|\n| Singapore      |  6167759 |  6167759 |\n| South Africa   |  6436807 |  6436807 |\n| Canada         |  6513813 |  6513813 |\n| Sudan          |  6778168 |  6778168 |\n| Spain          |  6826620 |  6826620 |\n| Chile          |  6973392 |  6973392 |\n| Hong Kong      |  7791531 |  7791531 |\n| Saudi Arabia   |  7964688 |  7964688 |\n| United States  |  7966324 |  7966324 |\n| Iraq           |  8154140 |  8154140 |\n| Tanzania       |  8529744 |  8529744 |\n| Malaysia       |  8980578 |  8980578 |\n| Iran           |  9738111 |  9738111 |\n| Vietnam        |  9798896 |  9798896 |\n| United Kingdom |  9818142 |  9818142 |\n| Angola         | 10049628 | 10049628 |\n| South Korea    | 10059272 | 10059272 |\n| France         | 11352823 | 11352823 |\n| Thailand       | 11415533 | 11415533 |\n| Peru           | 11529982 | 11529982 |\n| Indonesia      | 11628728 | 11628728 |\n| Colombia       | 11779275 | 11779275 |\n| Russia         | 12768223 | 12768223 |\n| Philippines    | 15211511 | 15211511 |\n| Argentina      | 15714124 | 15714124 |\n| Turkey         | 16211581 | 16211581 |\n| Nigeria        | 17124998 | 17124998 |\n| DR Congo       | 17815364 | 17815364 |\n| Pakistan       | 14835678 | 18059805 |\n| Mexico         | 22831373 | 22831373 |\n| Brazil         |  6360069 | 23045227 |\n| Egypt          | 23095986 | 23095986 |\n| Bangladesh     | 24561693 | 24561693 |\n| China          |  6242353 | 30365228 |\n| India          |  7498726 | 34598951 |\n| Japan          |  9544065 | 37131070 |\n#+END:\n\n* test ID\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x a g g <tab> <return>\"\n :isid       \"y\"\n :orgid      \"55ab27a2 <tab> <return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :precompute \"<return>\"\n :cols       \"ref SPC vsum(val) SPC count() <return>\"\n :cond       \"<return>\"\n :hline      \"<return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: aggregate :table \"55ab27a2-c44b-4a14-9ba4-f6879375207d\" :cols \"ref vsum(val) count()\"\n| ref  | vsum(val) | count() |\n|------+-----------+---------|\n| S    |    2490.9 |       4 |\n| TT   |    2352.3 |       6 |\n| UUU  |    1643.8 |       4 |\n| VVVV |    3144.5 |       5 |\n#+END:\n\n* transpose\n\n#+name: sunnydays\n| feat |   | mon | tue | wed | thu | fri | sat | sun |\n|------+---+-----+-----+-----+-----+-----+-----+-----|\n| rain |   |  no | yes | yes |  no |  no |  no | no  |\n| temp |   |  23 |  15 |  13 |  16 |  19 |  22 | 25  |\n| wind |   |  10 |   0 |  10 |  20 |  20 |  25 | 15  |\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x t r a n <tab> <return>\"\n :isid       \"n\"\n :file       \"<return>\"\n :name       \"C-a SPC C-a C-k s u n n y d <tab> <return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :cols       \"<return>\"\n :cond       \"<return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: transpose :table \"sunnydays\" :cols \"\"\n| feat |   | rain | temp | wind |\n|------+---+------+------+------|\n| mon  |   | no   |   23 |   10 |\n| tue  |   | yes  |   15 |    0 |\n| wed  |   | yes  |   13 |   10 |\n| thu  |   | no   |   16 |   20 |\n| fri  |   | no   |   19 |   20 |\n| sat  |   | no   |   22 |   25 |\n| sun  |   | no   |   25 |   15 |\n#+END:\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-u C-c C-x x tran <tab> <return>\"\n :isid       \"n\"\n :file       \"<return>\"\n :name       \"C-a SPC C-a C-k s u n n y d <tab> <return>\"\n :params     \"<return>\"\n :slice      \"<return>\"\n :cols       \"feat SPC sun SPC mon SPC sat <return>\"\n :cond       \"<return>\"\n :post       \"<return>\"\n)\n#+end_src\n#+BEGIN: transpose :table \"sunnydays\" :cols \"feat sun mon sat\"\n| feat |   | rain | temp | wind |\n| sun  |   | no   |   25 |   15 |\n| mon  |   | no   |   23 |   10 |\n| sat  |   | no   |   22 |   25 |\n#+END:\n\n* update preserving #+tblfm: formula\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-c C-x x aggreg <tab> <return>\"\n :file       \"C-a SPC C-a C-k distant-tests.org <return>\"\n :name       \"C-a SPC C-a C-k distanttabl <tab> <return>\"\n :slice      \"<return>\"\n :cols       \"<return>\"\n)\n#+end_src\n #+BEGIN: aggregate :table \"distant-tests.org:distanttable[0:10]\" :cols \"tag vsum(val)\" :formula \"$3=$2*10\"\n | tag | vsum(val) |        |         |\n |-----+-----------+--------+---------|\n | A   |    210.76 | 2107.6 | 12107.6 |\n | BB  |    257.22 | 2572.2 | 12572.2 |\n | CCC |     92.06 |  920.6 | 10920.6 |\n #+TBLFM: $3=$2*10::$4=$3+10000\n #+END:\n ▲\n ╰──indent in order to update the #+begin: line instead of creating it anew\n\n#+begin_src elisp :results none\n(orgtbl-aggregate-wizard-test\n :init       \"C-c C-x x transpose <tab> <return>\"\n :file       \"C-a SPC C-a C-k distant-tests.org <return>\"\n :name       \"C-a SPC C-a C-k distanttabl <tab> <return>\"\n :slice      \"<return>\"\n :cols       \"<return>\"\n)\n#+end_src\n #+BEGIN: transpose :table \"distant-tests.org:distanttable[0:5]\" :cols \"\"\n | tag |   |     A |    BB |   CCC |    BB | BB + 1000 |\n | val |   | 11.58 | 98.43 | 87.74 | 87.97 |   1087.97 |\n #+TBLFM: $7=$-1+1000\n #+END:\n ▲\n ╰──indent in order to update the #+begin: line instead of creating it anew\n"
  }
]