├── LICENSE
├── README.md
├── SimulationCode.R
├── pwrSEM.R
└── pwrSEM_demo.gif
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Power Analysis for Parameter Estimation in Structural Equation Modeling: A Discussion and Tutorial
2 |
3 | This repo contains code files referenced in the following [paper](https://psyarxiv.com/pj67b):
4 |
5 | Wang, Y. A., & Rhemtulla, M. (in press). Power analysis for parameter estimation in structural equation modeling: A discussion and tutorial. *Advances in Methods and Practices in Psychological Science*.
6 |
7 | ## pwrSEM
8 |
9 | [pwrSEM](https://yilinandrewang.shinyapps.io/pwrSEM/) is a Shiny web app developed by [Y. Andre Wang](http://yilinandrewang.com/) for power analysis for parameter estimation in structural equation modeling.
10 |
11 | 
12 |
13 | ## Code
14 |
15 | Source code of the app is available at: [pwrSEM.R](https://github.com/yilinandrewang/pwrSEM/blob/master/pwrSEM.R). Users can run pwrSEM locally in R Studio by downloading the source code file, opening it in R Studio, and then pressing “Run App” (procedure current as of R Studio version 1.2.5001).
16 |
17 | Code for the simulation studies reported in the paper is available at: [SimulationCode.R](https://github.com/yilinandrewang/pwrSEM/blob/master/SimulationCode.R).
18 |
19 | ## Version History
20 |
21 | Version 0.1.2 (released February 23, 2021):
22 | - Fixed an error where residual variances could not be calculated even when user-entered values were on standardized metric and produced a positive definite model-implied covariance matrix
23 | - Fixed an error in power estimation where the population values were misrepresented internally, resulting in inaccurate power estimates
24 |
25 | Version 0.1.1 (released March 31, 2020): Initial release
26 |
--------------------------------------------------------------------------------
/SimulationCode.R:
--------------------------------------------------------------------------------
1 | ## Power Analysis for Parameter Estimation in Structural Equation Modeling: ##
2 | ########################## A Discussion and Tutorial #########################
3 |
4 | # Simulation Study Code
5 | # Written by Y. Andre Wang and Mijke Rhemtulla
6 | # Updated on November 21, 2019
7 |
8 |
9 | # Note: The code for the Main Simulation Study, Supplemental Simulation Set 1,
10 | # Supplemental Simulation Set 2, and Single Predictor Simulation can be run
11 | # independently of each other.
12 |
13 |
14 | # Load package lavaan (and install it if not available)
15 | if (!"lavaan" %in% installed.packages()) install.packages("lavaan")
16 | library(lavaan)
17 |
18 |
19 | # Create a data frame of simulation conditions ----------------------------
20 |
21 | ct <- data.frame(
22 | ksim = 1000, # number of simulations is set to 1000
23 | sampleN = rep(rev(seq(from = 50, to = 1000, by = 50)), each = 20), # sample N
24 | lambda = rep(c(.3, .5, .7, .9), each = 5), # factor loading strength
25 | beta = c(.1, .2, .3, .4, .5)) # population effect size of X on Y
26 |
27 |
28 |
29 | # Main Simulation Study ---------------------------------------------------
30 |
31 |
32 | # *- Simulation function for p/f = 3 --------------------------------------
33 |
34 | ParaPower3 <- function(ksim, sampleN, lambda, beta, seed = 42) {
35 |
36 | # Specify model
37 | mod <- "
38 | X =~ x1 + x2 + x3
39 | W =~ w1 + w2 + w3
40 | Z =~ z1 + z2 + z3
41 | Y =~ y1 + y2 + y3
42 |
43 | Y ~ X + W + Z
44 | W ~~ X + Z
45 | X ~~ Z"
46 |
47 | # Calculate explained variance of Y
48 | ModVarY <- beta^2 + .1^2 + .2^2 + 2*beta*.1*.3 + 2*beta*.2*.3 + 2*.1*.2*.3
49 |
50 | # Assign population values
51 | PopMod.t <- lavaanify(mod)
52 | PopMod.t$ustart <- c(rep(lambda, 12), beta, .1, .2, rep(.3, 3),
53 | rep(1 - lambda^2, 12), rep(1, 3), 1 - ModVarY)
54 |
55 | # Define results objects for SEM and regression analyses
56 | lv_results <- NULL
57 | reg_results <- NULL
58 |
59 | # Simulate and fit data
60 | set.seed(seed)
61 |
62 | # Loop by iteration
63 | for (i in 1:ksim) {
64 |
65 | # Simulate and store data based on sample size input
66 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
67 |
68 | # Create composite scores
69 | data$X <- apply(data[, 1:3], 1, sum)
70 | data$W <- apply(data[, 4:6], 1, sum)
71 | data$Z <- apply(data[, 7:9], 1, sum)
72 | data$Y <- apply(data[, 10:12], 1, sum)
73 |
74 | # Fit regression model
75 | fitreg <- lm(Y ~ X + W + Z, data)
76 |
77 | # Fit structural equation model
78 | fit <- sem(model = mod, data = data, std.lv = TRUE)
79 |
80 | # Store estimates of the effect of X on Y from SEM
81 | lv_results <- rbind(lv_results, parameterEstimates(fit)[13, -c(6, 8:13)])
82 |
83 | # Store estimates of the effect of X on Y from regression
84 | reg_results <- rbind(reg_results,
85 | summary(fitreg)$coefficients["X", c(1:2, 4)])
86 | }
87 |
88 | # Reformat results from SEM
89 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
90 | row.names = 1:ksim)
91 |
92 | # Convergence rate
93 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
94 |
95 | # Power from SEM (denominator = # of converged cases)
96 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
97 |
98 | # Power from SEM (denominator = # all iterations)
99 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
100 |
101 | # Power from regression
102 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
103 |
104 | # Create table of power analysis results
105 | output <- data.frame(power, powerksim, conv, powerreg)
106 | return(list(output, results))
107 | }
108 |
109 | # CAUTION: Running the next function (currently as comment)
110 | # will write two files for each simulation condition and take many hours.
111 |
112 | # for(i in 1:nrow(ct)) {
113 | # dname <- paste("MS_p3", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
114 | # substr(ct[i, 4], 2, 4), sep = "")
115 | # test <- ParaPower3(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
116 | # write.table(test[1], sep = ",", row.names = F,
117 | # paste("results_", dname, ".csv", sep = "")) # power estimates
118 | # write.table(test[2], sep = ",", row.names = F,
119 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
120 | # }
121 |
122 |
123 | # *- Simulation function for p/f = 5 --------------------------------------
124 |
125 | ParaPower5 <- function(ksim, sampleN, lambda, beta, seed = 42) {
126 |
127 | # Specify model
128 | mod <- "
129 | X =~ x1 + x2 + x3 + x4 + x5
130 | W =~ w1 + w2 + w3 + w4 + w5
131 | Z =~ z1 + z2 + z3 + z4 + z5
132 | Y =~ y1 + y2 + y3 + y4 + y5
133 |
134 | Y ~ X + W + Z
135 | W ~~ X + Z
136 | X ~~ Z"
137 |
138 | # Calculate explained variance of Y
139 | ModVarY <- beta^2 + .1^2 + .2^2 + 2*beta*.1*.3 + 2*beta*.2*.3 + 2*.1*.2*.3
140 |
141 | # Assign population values
142 | PopMod.t <- lavaanify(mod)
143 | PopMod.t$ustart <- c(rep(lambda, 20), beta, .1, .2, rep(.3, 3),
144 | rep(1 - lambda^2, 20), rep(1, 3), 1 - ModVarY)
145 |
146 | # Define results objects for SEM and regression analyses
147 | lv_results <- NULL
148 | reg_results <- NULL
149 |
150 | # Simulate and fit data
151 | set.seed(seed)
152 | for (i in 1:ksim) {
153 |
154 | # Simulate data from the population model
155 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
156 |
157 | # Create composite scores
158 | data$X <- apply(data[, 1:5], 1, sum)
159 | data$W <- apply(data[, 6:10], 1, sum)
160 | data$Z <- apply(data[, 11:15], 1, sum)
161 | data$Y <- apply(data[, 16:20], 1, sum)
162 |
163 | # Fit regression model
164 | fitreg <- lm(Y ~ X + W + Z, data)
165 |
166 | # Fit structural equation model
167 | fit <- sem(model = mod, data = data, std.lv = TRUE)
168 |
169 | # Save estimates of the effect of X on Y from SEM
170 | lv_results <- rbind(lv_results, parameterEstimates(fit)[21, -c(6, 8:13)])
171 |
172 | # Save estimates of the effect of X on Y from regression
173 | reg_results <- rbind(reg_results,
174 | summary(fitreg)$coefficients["X", c(1:2, 4)])
175 | }
176 |
177 | # Reformat results from SEM
178 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
179 | row.names = 1:ksim)
180 |
181 | # Convergence rate
182 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
183 |
184 | # Power from SEM (denominator = # of converged cases)
185 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
186 |
187 | # Power from SEM (denominator = # all iterations)
188 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
189 |
190 | # Power from regression
191 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
192 |
193 | # Create table of power analysis results
194 | output <- data.frame(power, powerksim, conv, powerreg)
195 | return(list(output, results))
196 | }
197 |
198 | # CAUTION: Running the next function (currently as comment)
199 | # will write two files for each simulation condition and take many hours.
200 |
201 | # for(i in 1:nrow(ct)) {
202 | # dname <- paste("MS_p5", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
203 | # substr(ct[i, 4], 2, 4), sep = "")
204 | # test <- ParaPower5(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
205 | # write.table(test[1], sep = ",", row.names = F,
206 | # paste("results_", dname, ".csv", sep = "")) # power estimate
207 | # write.table(test[2], sep = ",", row.names = F,
208 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
209 | # }
210 |
211 |
212 | # *- Simulation function for p/f = 10 -------------------------------------
213 |
214 | ParaPower10 <- function(ksim, sampleN, lambda, beta, seed = 42) {
215 |
216 | # Specify model
217 | mod <- "
218 | X =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10
219 | W =~ w1 + w2 + w3 + w4 + w5 + w6 + w7 + w8 + w9 + w10
220 | Z =~ z1 + z2 + z3 + z4 + z5 + z6 + z7 + z8 + z9 + z10
221 | Y =~ y1 + y2 + y3 + y4 + y5 + y6 + y7 + y8 + y9 + y10
222 |
223 | Y ~ X + W + Z
224 | W ~~ X + Z
225 | X ~~ Z"
226 |
227 | # Calculate explained variance of Y
228 | ModVarY <- beta^2 + .1^2 + .2^2 + 2*beta*.1*.3 + 2*beta*.2*.3 + 2*.1*.2*.3
229 |
230 | # Assign population values
231 | PopMod.t <- lavaanify(mod)
232 | PopMod.t$ustart <- c(rep(lambda, 40), beta, .1, .2, rep(.3, 3),
233 | rep(1 - lambda^2, 40), rep(1, 3), 1 - ModVarY)
234 |
235 | # Define results objects for SEM and regression analyses
236 | lv_results <- NULL
237 | reg_results <- NULL
238 |
239 | # Simulate and fit data
240 | set.seed(seed)
241 |
242 | # Loop by iteration
243 | for (i in 1:ksim) {
244 |
245 | # Simulate and store data based on sample size input
246 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
247 |
248 | # Create composite scores
249 | data$X <- apply(data[, 1:10], 1, sum)
250 | data$W <- apply(data[, 11:20], 1, sum)
251 | data$Z <- apply(data[, 21:30], 1, sum)
252 | data$Y <- apply(data[, 31:40], 1, sum)
253 |
254 | # Fit regression model
255 | fitreg <- lm(Y ~ X + W + Z, data)
256 |
257 | # Fit structural equation model
258 | fit <- sem(model = mod, data = data, std.lv = TRUE)
259 |
260 | # Store estimates of the effect of X on Y from SEM
261 | lv_results <- rbind(lv_results, parameterEstimates(fit)[41, -c(6, 8:13)])
262 |
263 | # Store estimates of the effect of X on Y from regression
264 | reg_results <- rbind(reg_results,
265 | summary(fitreg)$coefficients["X", c(1:2, 4)])
266 | }
267 |
268 | # Reformat results from SEM
269 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
270 | row.names = 1:ksim)
271 |
272 | # Convergence rate
273 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
274 |
275 | # Power from SEM (denominator = # of converged cases)
276 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
277 |
278 | # Power from SEM (denominator = # all iterations)
279 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
280 |
281 | # Power from regression
282 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
283 |
284 | # Create table of power analysis results
285 | output <- data.frame(power, powerksim, conv, powerreg)
286 | return(list(output, results))
287 | }
288 |
289 | # CAUTION: Running the next function (currently as comment)
290 | # will write two files for each simulation condition and take many hours.
291 |
292 | # for(i in 1:nrow(ct)) {
293 | # dname <- paste("MS_p10", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
294 | # substr(ct[i, 4], 2, 4), sep = "")
295 | # test <- ParaPower10(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
296 | # write.table(test[1], sep = ",", row.names = F,
297 | # paste("results_", dname, ".csv", sep = "")) # power estimates
298 | # write.table(test[2], sep = ",", row.names = F,
299 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
300 | # }
301 |
302 |
303 |
304 | # Supplemental Simulation Set 1 -------------------------------------------
305 |
306 |
307 | # *- Simulation function for p/f = 3 --------------------------------------
308 |
309 | ParaPower3 <- function(ksim, sampleN, lambda, beta, seed = 42) {
310 |
311 | # Specify model
312 | mod <- "
313 | X =~ x1 + x2 + x3
314 | W =~ w1 + w2 + w3
315 | Z =~ z1 + z2 + z3
316 | Y =~ y1 + y2 + y3
317 |
318 | Y ~ X + W + Z
319 | W ~~ X + Z
320 | X ~~ Z"
321 |
322 | # Calculate explained variance of Y
323 | ModVarY <- beta^2 + .1^2 + .2^2 + 2*beta*.1*.5 + 2*beta*.2*.5 + 2*.1*.2*.5
324 |
325 | # Assign population values
326 | PopMod.t <- lavaanify(mod)
327 | PopMod.t$ustart <- c(rep(lambda, 12), beta, .1, .2, rep(.5, 3),
328 | rep(1 - lambda^2, 12), rep(1, 3), 1 - ModVarY)
329 |
330 | # Define results objects for SEM and regression analyses
331 | lv_results <- NULL
332 | reg_results <- NULL
333 |
334 | # Simulate and fit data
335 | set.seed(seed)
336 |
337 | # Loop by iteration
338 | for (i in 1:ksim) {
339 |
340 | # Simulate and store data based on sample size input
341 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
342 |
343 | # Create composite scores
344 | data$X <- apply(data[, 1:3], 1, sum)
345 | data$W <- apply(data[, 4:6], 1, sum)
346 | data$Z <- apply(data[, 7:9], 1, sum)
347 | data$Y <- apply(data[, 10:12], 1, sum)
348 |
349 | # Fit regression model
350 | fitreg <- lm(Y ~ X + W + Z, data)
351 |
352 | # Fit structural equation model
353 | fit <- sem(model = mod, data = data, std.lv = TRUE)
354 |
355 | # Store estimates of the effect of X on Y from SEM
356 | lv_results <- rbind(lv_results, parameterEstimates(fit)[13, -c(6, 8:13)])
357 |
358 | # Store estimates of the effect of X on Y from regression
359 | reg_results <- rbind(reg_results,
360 | summary(fitreg)$coefficients["X", c(1:2, 4)])
361 | }
362 |
363 | # Reformat results from SEM
364 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
365 | row.names = 1:ksim)
366 |
367 | # Convergence rate
368 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
369 |
370 | # Power from SEM (denominator = # of converged cases)
371 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
372 |
373 | # Power from SEM (denominator = # all iterations)
374 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
375 |
376 | # Power from regression
377 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
378 |
379 | # Create table of power analysis results
380 | output <- data.frame(power, powerksim, conv, powerreg)
381 | return(list(output, results))
382 | }
383 |
384 | # CAUTION: Running the next function (currently as comment)
385 | # will write two files for each simulation condition and take many hours.
386 |
387 | # for(i in 1:nrow(ct)) {
388 | # dname <- paste("SS1_p3", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
389 | # substr(ct[i, 4], 2, 4), sep = "")
390 | # test <- ParaPower3(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
391 | # write.table(test[1], sep = ",", row.names = F,
392 | # paste("results_", dname, ".csv", sep = "")) # power estimates
393 | # write.table(test[2], sep = ",", row.names = F,
394 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
395 | # }
396 |
397 |
398 | # *- Simulation function for p/f = 5 --------------------------------------
399 |
400 | ParaPower5 <- function(ksim, sampleN, lambda, beta, seed = 42) {
401 |
402 | # Specify model
403 | mod <- "
404 | X =~ x1 + x2 + x3 + x4 + x5
405 | W =~ w1 + w2 + w3 + w4 + w5
406 | Z =~ z1 + z2 + z3 + z4 + z5
407 | Y =~ y1 + y2 + y3 + y4 + y5
408 |
409 | Y ~ X + W + Z
410 | W ~~ X + Z
411 | X ~~ Z"
412 |
413 | # Calculate explained variance of Y
414 | ModVarY <- beta^2 + .1^2 + .2^2 + 2*beta*.1*.5 + 2*beta*.2*.5 + 2*.1*.2*.5
415 |
416 | # Assign population values
417 | PopMod.t <- lavaanify(mod)
418 | PopMod.t$ustart <- c(rep(lambda, 20), beta, .1, .2, rep(.5, 3),
419 | rep(1 - lambda^2, 20), rep(1, 3), 1 - ModVarY)
420 |
421 | # Define results objects for SEM and regression analyses
422 | lv_results <- NULL
423 | reg_results <- NULL
424 |
425 | # Simulate and fit data
426 | set.seed(seed)
427 | for (i in 1:ksim) {
428 |
429 | # Simulate data from the population model
430 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
431 |
432 | # Create composite scores
433 | data$X <- apply(data[, 1:5], 1, sum)
434 | data$W <- apply(data[, 6:10], 1, sum)
435 | data$Z <- apply(data[, 11:15], 1, sum)
436 | data$Y <- apply(data[, 16:20], 1, sum)
437 |
438 | # Fit regression model
439 | fitreg <- lm(Y ~ X + W + Z, data)
440 |
441 | # Fit structural equation model
442 | fit <- sem(model = mod, data = data, std.lv = TRUE)
443 |
444 | # Save estimates of the effect of X on Y from SEM
445 | lv_results <- rbind(lv_results, parameterEstimates(fit)[21, -c(6, 8:13)])
446 |
447 | # Save estimates of the effect of X on Y from regression
448 | reg_results <- rbind(reg_results,
449 | summary(fitreg)$coefficients["X", c(1:2, 4)])
450 | }
451 |
452 | # Reformat results from SEM
453 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
454 | row.names = 1:ksim)
455 |
456 | # Convergence rate
457 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
458 |
459 | # Power from SEM (denominator = # of converged cases)
460 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
461 |
462 | # Power from SEM (denominator = # all iterations)
463 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
464 |
465 | # Power from regression
466 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
467 |
468 | # Create table of power analysis results
469 | output <- data.frame(power, powerksim, conv, powerreg)
470 | return(list(output, results))
471 | }
472 |
473 | # CAUTION: Running the next function (currently as comment)
474 | # will write two files for each simulation condition and take many hours.
475 |
476 | # for(i in 1:nrow(ct)) {
477 | # dname <- paste("SS1_p5", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
478 | # substr(ct[i, 4], 2, 4), sep = "")
479 | # test <- ParaPower5(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
480 | # write.table(test[1], sep = ",", row.names = F,
481 | # paste("results_", dname, ".csv", sep = "")) # power estimate
482 | # write.table(test[2], sep = ",", row.names = F,
483 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
484 | # }
485 |
486 |
487 | # *- Simulation function for p/f = 10 -------------------------------------
488 |
489 | ParaPower10 <- function(ksim, sampleN, lambda, beta, seed = 42) {
490 |
491 | # Specify model
492 | mod <- "
493 | X =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10
494 | W =~ w1 + w2 + w3 + w4 + w5 + w6 + w7 + w8 + w9 + w10
495 | Z =~ z1 + z2 + z3 + z4 + z5 + z6 + z7 + z8 + z9 + z10
496 | Y =~ y1 + y2 + y3 + y4 + y5 + y6 + y7 + y8 + y9 + y10
497 |
498 | Y ~ X + W + Z
499 | W ~~ X + Z
500 | X ~~ Z"
501 |
502 | # Calculate explained variance of Y
503 | ModVarY <- beta^2 + .1^2 + .2^2 + 2*beta*.1*.5 + 2*beta*.2*.5 + 2*.1*.2*.5
504 |
505 | # Assign population values
506 | PopMod.t <- lavaanify(mod)
507 | PopMod.t$ustart <- c(rep(lambda, 40), beta, .1, .2, rep(.5, 3),
508 | rep(1 - lambda^2, 40), rep(1, 3), 1 - ModVarY)
509 |
510 | # Define results objects for SEM and regression analyses
511 | lv_results <- NULL
512 | reg_results <- NULL
513 |
514 | # Simulate and fit data
515 | set.seed(seed)
516 |
517 | # Loop by iteration
518 | for (i in 1:ksim) {
519 |
520 | # Simulate and store data based on sample size input
521 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
522 |
523 | # Create composite scores
524 | data$X <- apply(data[, 1:10], 1, sum)
525 | data$W <- apply(data[, 11:20], 1, sum)
526 | data$Z <- apply(data[, 21:30], 1, sum)
527 | data$Y <- apply(data[, 31:40], 1, sum)
528 |
529 | # Fit regression model
530 | fitreg <- lm(Y ~ X + W + Z, data)
531 |
532 | # Fit structural equation model
533 | fit <- sem(model = mod, data = data, std.lv = TRUE)
534 |
535 | # Store estimates of the effect of X on Y from SEM
536 | lv_results <- rbind(lv_results, parameterEstimates(fit)[41, -c(6, 8:13)])
537 |
538 | # Store estimates of the effect of X on Y from regression
539 | reg_results <- rbind(reg_results,
540 | summary(fitreg)$coefficients["X", c(1:2, 4)])
541 | }
542 |
543 | # Reformat results from SEM
544 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
545 | row.names = 1:ksim)
546 |
547 | # Convergence rate
548 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
549 |
550 | # Power from SEM (denominator = # of converged cases)
551 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
552 |
553 | # Power from SEM (denominator = # all iterations)
554 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
555 |
556 | # Power from regression
557 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
558 |
559 | # Create table of power analysis results
560 | output <- data.frame(power, powerksim, conv, powerreg)
561 | return(list(output, results))
562 | }
563 |
564 | # CAUTION: Running the next function (currently as comment)
565 | # will write two files for each simulation condition and take many hours.
566 |
567 | # for(i in 1:nrow(ct)) {
568 | # dname <- paste("SS1_p10", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
569 | # substr(ct[i, 4], 2, 4), sep = "")
570 | # test <- ParaPower10(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
571 | # write.table(test[1], sep = ",", row.names = F,
572 | # paste("results_", dname, ".csv", sep = "")) # power estimates
573 | # write.table(test[2], sep = ",", row.names = F,
574 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
575 | # }
576 |
577 |
578 |
579 | # Supplemental Simulation Set 2 -------------------------------------------
580 |
581 |
582 | # *- Simulation function for p/f = 3 --------------------------------------
583 |
584 | ParaPower3 <- function(ksim, sampleN, lambda, beta, seed = 42) {
585 |
586 | # Specify model
587 | mod <- "
588 | X =~ x1 + x2 + x3
589 | W =~ w1 + w2 + w3
590 | Z =~ z1 + z2 + z3
591 | Y =~ y1 + y2 + y3
592 |
593 | Y ~ X + W + Z
594 | W ~~ X + Z
595 | X ~~ Z"
596 |
597 | # Calculate explained variance of Y
598 | ModVarY <- beta^2 + .3^2 + .3^2 + 2*beta*.3*.3 + 2*beta*.3*.3 + 2*.3*.3*.3
599 |
600 | # Assign population values
601 | PopMod.t <- lavaanify(mod)
602 | PopMod.t$ustart <- c(rep(lambda, 12), beta, .3, .3, rep(.3, 3),
603 | rep(1 - lambda^2, 12), rep(1, 3), 1 - ModVarY)
604 |
605 | # Define results objects for SEM and regression analyses
606 | lv_results <- NULL
607 | reg_results <- NULL
608 |
609 | # Simulate and fit data
610 | set.seed(seed)
611 |
612 | # Loop by iteration
613 | for (i in 1:ksim) {
614 |
615 | # Simulate and store data based on sample size input
616 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
617 |
618 | # Create composite scores
619 | data$X <- apply(data[, 1:3], 1, sum)
620 | data$W <- apply(data[, 4:6], 1, sum)
621 | data$Z <- apply(data[, 7:9], 1, sum)
622 | data$Y <- apply(data[, 10:12], 1, sum)
623 |
624 | # Fit regression model
625 | fitreg <- lm(Y ~ X + W + Z, data)
626 |
627 | # Fit structural equation model
628 | fit <- sem(model = mod, data = data, std.lv = TRUE)
629 |
630 | # Store estimates of the effect of X on Y from SEM
631 | lv_results <- rbind(lv_results, parameterEstimates(fit)[13, -c(6, 8:13)])
632 |
633 | # Store estimates of the effect of X on Y from regression
634 | reg_results <- rbind(reg_results,
635 | summary(fitreg)$coefficients["X", c(1:2, 4)])
636 | }
637 |
638 | # Reformat results from SEM
639 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
640 | row.names = 1:ksim)
641 |
642 | # Convergence rate
643 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
644 |
645 | # Power from SEM (denominator = # of converged cases)
646 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
647 |
648 | # Power from SEM (denominator = # all iterations)
649 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
650 |
651 | # Power from regression
652 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
653 |
654 | # Create table of power analysis results
655 | output <- data.frame(power, powerksim, conv, powerreg)
656 | return(list(output, results))
657 | }
658 |
659 | # CAUTION: Running the next function (currently as comment)
660 | # will write two files for each simulation condition and take many hours.
661 |
662 | # for(i in 1:nrow(ct)) {
663 | # dname <- paste("SS2_p3", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
664 | # substr(ct[i, 4], 2, 4), sep = "")
665 | # test <- ParaPower3(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
666 | # write.table(test[1], sep = ",", row.names = F,
667 | # paste("results_", dname, ".csv", sep = "")) # power estimates
668 | # write.table(test[2], sep = ",", row.names = F,
669 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
670 | # }
671 |
672 |
673 | # *- Simulation function for p/f = 5 --------------------------------------
674 |
675 | ParaPower5 <- function(ksim, sampleN, lambda, beta, seed = 42) {
676 |
677 | # Specify model
678 | mod <- "
679 | X =~ x1 + x2 + x3 + x4 + x5
680 | W =~ w1 + w2 + w3 + w4 + w5
681 | Z =~ z1 + z2 + z3 + z4 + z5
682 | Y =~ y1 + y2 + y3 + y4 + y5
683 |
684 | Y ~ X + W + Z
685 | W ~~ X + Z
686 | X ~~ Z"
687 |
688 | # Calculate explained variance of Y
689 | ModVarY <- beta^2 + .3^2 + .3^2 + 2*beta*.3*.3 + 2*beta*.3*.3 + 2*.3*.3*.3
690 |
691 | # Assign population values
692 | PopMod.t <- lavaanify(mod)
693 | PopMod.t$ustart <- c(rep(lambda, 20), beta, .3, .3, rep(.3, 3),
694 | rep(1 - lambda^2, 20), rep(1, 3), 1 - ModVarY)
695 |
696 | # Define results objects for SEM and regression analyses
697 | lv_results <- NULL
698 | reg_results <- NULL
699 |
700 | # Simulate and fit data
701 | set.seed(seed)
702 | for (i in 1:ksim) {
703 |
704 | # Simulate data from the population model
705 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
706 |
707 | # Create composite scores
708 | data$X <- apply(data[, 1:5], 1, sum)
709 | data$W <- apply(data[, 6:10], 1, sum)
710 | data$Z <- apply(data[, 11:15], 1, sum)
711 | data$Y <- apply(data[, 16:20], 1, sum)
712 |
713 | # Fit regression model
714 | fitreg <- lm(Y ~ X + W + Z, data)
715 |
716 | # Fit structural equation model
717 | fit <- sem(model = mod, data = data, std.lv = TRUE)
718 |
719 | # Save estimates of the effect of X on Y from SEM
720 | lv_results <- rbind(lv_results, parameterEstimates(fit)[21, -c(6, 8:13)])
721 |
722 | # Save estimates of the effect of X on Y from regression
723 | reg_results <- rbind(reg_results,
724 | summary(fitreg)$coefficients["X", c(1:2, 4)])
725 | }
726 |
727 | # Reformat results from SEM
728 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
729 | row.names = 1:ksim)
730 |
731 | # Convergence rate
732 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
733 |
734 | # Power from SEM (denominator = # of converged cases)
735 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
736 |
737 | # Power from SEM (denominator = # all iterations)
738 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
739 |
740 | # Power from regression
741 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
742 |
743 | # Create table of power analysis results
744 | output <- data.frame(power, powerksim, conv, powerreg)
745 | return(list(output, results))
746 | }
747 |
748 | # CAUTION: Running the next function (currently as comment)
749 | # will write two files for each simulation condition and take many hours.
750 |
751 | # for(i in 1:nrow(ct)) {
752 | # dname <- paste("SS2_p5", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
753 | # substr(ct[i, 4], 2, 4), sep = "")
754 | # test <- ParaPower5(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
755 | # write.table(test[1], sep = ",", row.names = F,
756 | # paste("results_", dname, ".csv", sep = "")) # power estimate
757 | # write.table(test[2], sep = ",", row.names = F,
758 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
759 | # }
760 |
761 |
762 | # *- Simulation function for p/f = 10 -------------------------------------
763 |
764 | ParaPower10 <- function(ksim, sampleN, lambda, beta, seed = 42) {
765 |
766 | # Specify model
767 | mod <- "
768 | X =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10
769 | W =~ w1 + w2 + w3 + w4 + w5 + w6 + w7 + w8 + w9 + w10
770 | Z =~ z1 + z2 + z3 + z4 + z5 + z6 + z7 + z8 + z9 + z10
771 | Y =~ y1 + y2 + y3 + y4 + y5 + y6 + y7 + y8 + y9 + y10
772 |
773 | Y ~ X + W + Z
774 | W ~~ X + Z
775 | X ~~ Z"
776 |
777 | # Calculate explained variance of Y
778 | ModVarY <- beta^2 + .3^2 + .3^2 + 2*beta*.3*.3 + 2*beta*.3*.3 + 2*.3*.3*.3
779 |
780 | # Assign population values
781 | PopMod.t <- lavaanify(mod)
782 | PopMod.t$ustart <- c(rep(lambda, 40), beta, .3, .3, rep(.3, 3),
783 | rep(1 - lambda^2, 40), rep(1, 3), 1 - ModVarY)
784 |
785 | # Define results objects for SEM and regression analyses
786 | lv_results <- NULL
787 | reg_results <- NULL
788 |
789 | # Simulate and fit data
790 | set.seed(seed)
791 |
792 | # Loop by iteration
793 | for (i in 1:ksim) {
794 |
795 | # Simulate and store data based on sample size input
796 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
797 |
798 | # Create composite scores
799 | data$X <- apply(data[, 1:10], 1, sum)
800 | data$W <- apply(data[, 11:20], 1, sum)
801 | data$Z <- apply(data[, 21:30], 1, sum)
802 | data$Y <- apply(data[, 31:40], 1, sum)
803 |
804 | # Fit regression model
805 | fitreg <- lm(Y ~ X + W + Z, data)
806 |
807 | # Fit structural equation model
808 | fit <- sem(model = mod, data = data, std.lv = TRUE)
809 |
810 | # Store estimates of the effect of X on Y from SEM
811 | lv_results <- rbind(lv_results, parameterEstimates(fit)[41, -c(6, 8:13)])
812 |
813 | # Store estimates of the effect of X on Y from regression
814 | reg_results <- rbind(reg_results,
815 | summary(fitreg)$coefficients["X", c(1:2, 4)])
816 | }
817 |
818 | # Reformat results from SEM
819 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
820 | row.names = 1:ksim)
821 |
822 | # Convergence rate
823 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
824 |
825 | # Power from SEM (denominator = # of converged cases)
826 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
827 |
828 | # Power from SEM (denominator = # all iterations)
829 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
830 |
831 | # Power from regression
832 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
833 |
834 | # Create table of power analysis results
835 | output <- data.frame(power, powerksim, conv, powerreg)
836 | return(list(output, results))
837 | }
838 |
839 | # CAUTION: Running the next function (currently as comment)
840 | # will write two files for each simulation condition and take many hours.
841 |
842 | # for(i in 1:nrow(ct)) {
843 | # dname <- paste("SS2_p10", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
844 | # substr(ct[i, 4], 2, 4), sep = "")
845 | # test <- ParaPower10(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
846 | # write.table(test[1], sep = ",", row.names = F,
847 | # paste("results_", dname, ".csv", sep = "")) # power estimates
848 | # write.table(test[2], sep = ",", row.names = F,
849 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
850 | # }
851 |
852 |
853 |
854 | # Single Predictor Simulation Set -----------------------------------------
855 |
856 |
857 | # *- Simulation function for p/f = 3 --------------------------------------
858 |
859 | ParaPower3 <- function(ksim, sampleN, lambda, beta, seed = 42) {
860 |
861 | # Specify model
862 | mod <- "
863 | X =~ x1 + x2 + x3
864 | Y =~ y1 + y2 + y3
865 |
866 | Y ~ X"
867 |
868 | # Calculate explained variance of Y
869 | ModVarY <- beta^2
870 |
871 | # Assign population values
872 | PopMod.t <- lavaanify(mod)
873 | PopMod.t$ustart <- c(rep(lambda, 6), beta, rep(1 - lambda^2, 6), 1,
874 | 1 - ModVarY)
875 |
876 | # Define results objects for SEM and regression analyses
877 | lv_results <- NULL
878 | reg_results <- NULL
879 |
880 | # Simulate and fit data
881 | set.seed(seed)
882 |
883 | # Loop by iteration
884 | for (i in 1:ksim) {
885 |
886 | # Simulate and store data based on sample size input
887 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
888 |
889 | # Create composite scores
890 | data$X <- apply(data[, 1:3], 1, sum)
891 | data$Y <- apply(data[, 4:6], 1, sum)
892 |
893 | # Fit regression model
894 | fitreg <- lm(Y ~ X, data)
895 |
896 | # Fit structural equation model
897 | fit <- sem(model = mod, data = data, std.lv = TRUE)
898 |
899 | # Store estimates of the effect of X on Y from SEM
900 | lv_results <- rbind(lv_results, parameterEstimates(fit)[7, -c(6, 8:13)])
901 |
902 | # Store estimates of the effect of X on Y from regression
903 | reg_results <- rbind(reg_results,
904 | summary(fitreg)$coefficients["X", c(1:2, 4)])
905 | }
906 |
907 | # Reformat results from SEM
908 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
909 | row.names = 1:ksim)
910 |
911 | # Convergence rate
912 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
913 |
914 | # Power from SEM (denominator = # of converged cases)
915 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
916 |
917 | # Power from SEM (denominator = # all iterations)
918 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
919 |
920 | # Power from regression
921 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
922 |
923 | # Create table of power analysis results
924 | output <- data.frame(power, powerksim, conv, powerreg)
925 | return(list(output, results))
926 | }
927 |
928 | # CAUTION: Running the next function (currently as comment)
929 | # will write two files for each simulation condition and take many hours.
930 |
931 | # for(i in 1:nrow(ct)) {
932 | # dname <- paste("SP_p3", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
933 | # substr(ct[i, 4], 2, 4), sep = "")
934 | # test <- ParaPower3(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
935 | # write.table(test[1], sep = ",", row.names = F,
936 | # paste("results_", dname, ".csv", sep = "")) # power estimates
937 | # write.table(test[2], sep = ",", row.names = F,
938 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
939 | # }
940 |
941 |
942 |
943 | # *- Simulation function for p/f = 5 --------------------------------------
944 |
945 | ParaPower5 <- function(ksim, sampleN, lambda, beta, seed = 42) {
946 |
947 | # Specify model
948 | mod <- "
949 | X =~ x1 + x2 + x3 + x4 + x5
950 | Y =~ y1 + y2 + y3 + y4 + y5
951 |
952 | Y ~ X"
953 |
954 | # Calculate explained variance of Y
955 | ModVarY <- beta^2
956 |
957 | # Assign population values
958 | PopMod.t <- lavaanify(mod)
959 | PopMod.t$ustart <- c(rep(lambda, 10), beta, rep(1 - lambda^2, 10), 1,
960 | 1 - ModVarY)
961 |
962 | # Define results objects for SEM and regression analyses
963 | lv_results <- NULL
964 | reg_results <- NULL
965 |
966 | # Simulate and fit data
967 | set.seed(seed)
968 | for (i in 1:ksim) {
969 |
970 | # Simulate data from the population model
971 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
972 |
973 | # Create composite scores
974 | data$X <- apply(data[, 1:5], 1, sum)
975 | data$Y <- apply(data[, 6:10], 1, sum)
976 |
977 | # Fit regression model
978 | fitreg <- lm(Y ~ X, data)
979 |
980 | # Fit structural equation model
981 | fit <- sem(model = mod, data = data, std.lv = TRUE)
982 |
983 | # Save estimates of the effect of X on Y from SEM
984 | lv_results <- rbind(lv_results, parameterEstimates(fit)[11, -c(6, 8:13)])
985 |
986 | # Save estimates of the effect of X on Y from regression
987 | reg_results <- rbind(reg_results,
988 | summary(fitreg)$coefficients["X", c(1:2, 4)])
989 | }
990 |
991 | # Reformat results from SEM
992 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
993 | row.names = 1:ksim)
994 |
995 | # Convergence rate
996 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
997 |
998 | # Power from SEM (denominator = # of converged cases)
999 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
1000 |
1001 | # Power from SEM (denominator = # all iterations)
1002 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
1003 |
1004 | # Power from regression
1005 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
1006 |
1007 | # Create table of power analysis results
1008 | output <- data.frame(power, powerksim, conv, powerreg)
1009 | return(list(output, results))
1010 | }
1011 |
1012 | # CAUTION: Running the next function (currently as comment)
1013 | # will write two files for each simulation condition and take many hours.
1014 |
1015 | # for(i in 1:nrow(ct)) {
1016 | # dname <- paste("SP_p5", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
1017 | # substr(ct[i, 4], 2, 4), sep = "")
1018 | # test <- ParaPower5(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
1019 | # write.table(test[1], sep = ",", row.names = F,
1020 | # paste("results_", dname, ".csv", sep = "")) # power estimate
1021 | # write.table(test[2], sep = ",", row.names = F,
1022 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
1023 | # }
1024 |
1025 |
1026 |
1027 | # *- Simulation function for p/f = 10 -------------------------------------
1028 |
1029 | ParaPower10 <- function(ksim, sampleN, lambda, beta, seed = 42) {
1030 |
1031 | # Specify model
1032 | mod <- "
1033 | X =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10
1034 | Y =~ y1 + y2 + y3 + y4 + y5 + y6 + y7 + y8 + y9 + y10
1035 |
1036 | Y ~ X"
1037 |
1038 | # Calculate explained variance of Y
1039 | ModVarY <- beta^2
1040 |
1041 | # Assign population values
1042 | PopMod.t <- lavaanify(mod)
1043 | PopMod.t$ustart <- c(rep(lambda, 20), beta, rep(1 - lambda^2, 20), 1,
1044 | 1 - ModVarY)
1045 |
1046 | # Define results objects for SEM and regression analyses
1047 | lv_results <- NULL
1048 | reg_results <- NULL
1049 |
1050 | # Simulate and fit data
1051 | set.seed(seed)
1052 |
1053 | # Loop by iteration
1054 | for (i in 1:ksim) {
1055 |
1056 | # Simulate and store data based on sample size input
1057 | data <- as.data.frame(simulateData(PopMod.t, sample.nobs = sampleN))
1058 |
1059 | # Create composite scores
1060 | data$X <- apply(data[, 1:10], 1, sum)
1061 | data$Y <- apply(data[, 11:20], 1, sum)
1062 |
1063 | # Fit regression model
1064 | fitreg <- lm(Y ~ X, data)
1065 |
1066 | # Fit structural equation model
1067 | fit <- sem(model = mod, data = data, std.lv = TRUE)
1068 |
1069 | # Store estimates of the effect of X on Y from SEM
1070 | lv_results <- rbind(lv_results, parameterEstimates(fit)[21, -c(6, 8:13)])
1071 |
1072 | # Store estimates of the effect of X on Y from regression
1073 | reg_results <- rbind(reg_results,
1074 | summary(fitreg)$coefficients["X", c(1:2, 4)])
1075 | }
1076 |
1077 | # Reformat results from SEM
1078 | results <- as.data.frame(cbind(lv_results, reg = "Y ~ X", reg_results),
1079 | row.names = 1:ksim)
1080 |
1081 | # Convergence rate
1082 | conv <- (ksim - sum(is.na(lv_results$pvalue)))/ksim
1083 |
1084 | # Power from SEM (denominator = # of converged cases)
1085 | power <- length(which(lv_results$pvalue < 0.05))/(conv*ksim)
1086 |
1087 | # Power from SEM (denominator = # all iterations)
1088 | powerksim <- length(which(lv_results$pvalue < 0.05))/ksim
1089 |
1090 | # Power from regression
1091 | powerreg <- length(which(reg_results[, 3] < 0.05))/ksim
1092 |
1093 | # Create table of power analysis results
1094 | output <- data.frame(power, powerksim, conv, powerreg)
1095 | return(list(output, results))
1096 | }
1097 |
1098 | # CAUTION: Running the next function (currently as comment)
1099 | # will write two files for each simulation condition and take many hours.
1100 |
1101 | # for(i in 1:nrow(ct)) {
1102 | # dname <- paste("SP_p10", "_", ct[i, 2], "_", substr(ct[i, 3], 2, 4), "_",
1103 | # substr(ct[i, 4], 2, 4), sep = "")
1104 | # test <- ParaPower10(ct[i, 1], ct[i, 2], ct[i, 3], ct[i, 4])
1105 | # write.table(test[1], sep = ",", row.names = F,
1106 | # paste("results_", dname, ".csv", sep = "")) # power estimates
1107 | # write.table(test[2], sep = ",", row.names = F,
1108 | # paste("data_", dname, ".csv", sep = "")) # parameter estimates
1109 | # }
1110 |
--------------------------------------------------------------------------------
/pwrSEM.R:
--------------------------------------------------------------------------------
1 | ############################# pwrSEM #############################
2 | # Power Analysis for Parameter Estimation in Structural Equation Modeling ##
3 |
4 | # Written by Y. Andre Wang
5 |
6 | # Load packages
7 | if(!require(shiny)){install.packages('shiny')}
8 | if(!require(lavaan)){install.packages('lavaan')}
9 | if(!require(semPlot)){install.packages('semPlot')}
10 | if(!require(rhandsontable)){install.packages('rhandsontable')}
11 | if(!require(semTools)){install.packages('semTools')}
12 | if(!require(tidyr)){install.packages('tidyr')}
13 | library(shiny); library(lavaan); library(semPlot); library(rhandsontable)
14 | library(semTools); library(tidyr)
15 |
16 |
17 | # Define UI ---------------------------------------------------------------
18 |
19 | ui <- fluidPage(
20 |
21 |
22 | # *- Set button and text colors -------------------------------------------
23 |
24 | tags$head(
25 | tags$style(HTML('#clicks1{background-color:#428BCA; color: white}',
26 | '#tab2to3{background-color:#428BCA; color: white}',
27 | '#tab3to4{background-color:#428BCA; color: white}',
28 | '#autoRes{background-color:#4CAF50; color: white}',
29 | '#sim{background-color:#428BCA; color: white}',
30 | '#resid_warning{color: red}',
31 | '#resid_std{color: red}',
32 | '#resid_success{color: green}',
33 | '#step3_para_warning{color: red}',
34 | '#step3_para_all{color: red}',
35 | '#step3_dim_warning{color: red}',
36 | '#step3_para_success{color: blue}',
37 | '#step3_model_warning{color: red}',
38 | '#step4_para_warning{color: red}',
39 | '#step4_para_all{color: red}',
40 | '#step4_model_warning{color: red}',
41 | '#step4_dim_warning{color: red}'))
42 | ),
43 |
44 |
45 | # *- Set app header -------------------------------------------------------
46 |
47 | fluidRow(
48 | column(width = 12,
49 | HTML(paste(tags$strong("pwrSEM", style = "font-size:40px;"),
50 | "v0.1.2")),
51 | h4("Power Analysis for Parameter Estimation in Structural Equation Modeling"),
52 | HTML(paste(
53 | "If you find this app useful, please cite: Wang, Y. A., & ",
54 | "Rhemtulla, M. (", tags$a(
55 | href = "https://psyarxiv.com/pj67b",
56 | "in press"),
57 | "). Power analysis for parameter estimation in structural ",
58 | "equation modeling: A discussion and tutorial. ",
59 | tags$i("Advances in Methods and Practices in Psychological Science."),
60 | sep = "")),
61 | style = "padding-bottom: 10px;"
62 | )
63 | ),
64 |
65 |
66 | # *- Set sidebar with "how to" guide for the app --------------------------
67 |
68 | sidebarLayout(
69 | sidebarPanel(
70 | h4("How to Use This App"),
71 |
72 | # Step 1
73 | tags$div(
74 | HTML(paste(
75 | tags$b('Step 1. Specify Model'),
76 | '. Enter your analysis model using lavaan syntax. Examples of ',
77 | 'formula types that define a structural equation model include ',
78 | '(more information',
79 | tags$a(href = "http://lavaan.ugent.be/tutorial/syntax1.html",
80 | " here"), "):",
81 | sep = "")
82 | )
83 | ),
84 |
85 | tags$ul(
86 | tags$li(tags$code("=~"), '"is measured by"'),
87 | tags$li(tags$code("~"), '"is regressed on"'),
88 | tags$li(tags$code("~~"), '"is correlated with"')
89 | ),
90 | p('Click "Set Model" to set the analysis model and continue to Step 2.'
91 | ),
92 |
93 | # Step 2
94 | tags$div(
95 | HTML(paste(
96 | tags$b('Step 2. Visualize'),
97 | '. Ensure that the visualized model looks right, then click ',
98 | '"Proceed" to continue to Step 3. ', sep = "")),
99 | style = "padding-bottom: 10px;"
100 | ),
101 |
102 | # Step 3
103 | tags$div(
104 | HTML(paste(
105 | tags$b('Step 3. Set Parameter Values'),
106 | '. Fill in the "Value" column with the ',
107 | 'population value for each parameter, then check the boxes in the ',
108 | '"Effect" column for the parameters you would like to detect. ',
109 | 'Click "Confirm Parameter Values" to continue to Step 4.',
110 | sep = "")),
111 | style = "padding-bottom: 10px;"
112 | ),
113 |
114 | # Step 4
115 | tags$div(
116 | HTML(paste(
117 | tags$b('Step 4. Estimate Power'),
118 | '. Set your sample size and number of simulations, then click ',
119 | '"Estimate Power via Simulations" to run your power analysis.',
120 | sep = "")
121 | )
122 | )
123 | )
124 | ,
125 |
126 |
127 | # *- Set main interface ---------------------------------------------------
128 |
129 | mainPanel(
130 | tabsetPanel(
131 | id = "tabby",
132 |
133 |
134 | # *--- Step 1 -------------------------------------------------------------
135 |
136 | tabPanel(
137 | "1. Specify Model", value = "tab1",
138 | br(),
139 | column(
140 | 8,
141 | wellPanel(
142 |
143 | # Create text box for users to enter analysis model
144 | textAreaInput(
145 | inputId = "text1",
146 | label = "Enter your analysis model below:",
147 |
148 | # Pre-fill with sample syntax
149 | value = "
150 | X =~ x1 + x2 + x3
151 | Y =~ y1 + y2 + y3
152 |
153 | Y ~ X",
154 | # Allow users to resize text box
155 | resize = "both", rows = 12, cols = 80),
156 |
157 | # Add radio button for scale setting
158 | radioButtons(
159 | inputId = "stdlv.radio",
160 | label = "How would you like to set the scale of your latent factors?",
161 | choices = list("Fix variances of latent variables" = 1,
162 | "Fix first factor loadings" = 2),
163 | selected = 1),
164 |
165 | # Add button for model setting
166 | actionButton(
167 | inputId = "clicks1",
168 | label = "Set Model")
169 | )
170 | )
171 | ),
172 |
173 |
174 | # *--- Step 2 -------------------------------------------------------------
175 |
176 | tabPanel(
177 | "2. Visualize", value = "tab2",
178 |
179 | # Display model diagram
180 | plotOutput("plot"),
181 | helpText('Dotted edges represent fixed parameters; solid edges',
182 | 'represent free parameters.'),
183 |
184 | # Display visualization options
185 | fluidRow(
186 | column(4,
187 | radioButtons(inputId = "structural",
188 | label = "Show measurement model?",
189 | choices = list("Yes" = 1,
190 | "No" = 2),
191 | selected = 1, inline = T)),
192 | column(3,
193 | numericInput(inputId = "sizeMan",
194 | label = "Size of manifest nodes",
195 | value = 5, min = 1, step = 1,
196 | max = 15)),
197 | column(3,
198 | numericInput(inputId = "sizeLat",
199 | label = "Size of latent nodes",
200 | value = 8, min = 1, step = 1,
201 | max = 15)),
202 | column(2,
203 | numericInput(inputId = "rotation",
204 | label = "Rotation",
205 | value = 2, min = 1, step = 1,
206 | max = 4))
207 | ),
208 |
209 | # Add navigation buttons
210 | actionButton(inputId = "tab2to1",
211 | label = "Back to Step 1"),
212 | actionButton(inputId = "tab2to3",
213 | label = "Proceed")
214 | ),
215 |
216 |
217 | # *--- Step 3 -------------------------------------------------------------
218 |
219 | tabPanel(
220 | "3. Set Parameter Values", value = "tab3",
221 |
222 | # Display instructions
223 | helpText('Your model parameter table is shown below.',
224 | 'You can use it like an Excel spreadsheet.',
225 | '(e.g., double-click on a "Value" cell to edit).',
226 | br(),
227 | 'Not sure what values to set the parameters at?',
228 | tags$ul(
229 | tags$li('If you need help with setting factor',
230 | 'loadings or latent regression coefficients,',
231 | 'click the "Help" tab for suggestions.'),
232 | tags$li('If you need help with setting residual',
233 | 'variances, enter factor loadings and regression',
234 | 'coefficients in the standardized metric, ',
235 | tags$i('leave blank all other parameters,'),
236 | 'then click "Set Residual Variances for Me"',
237 | 'below. (Note that covariance parameters, if any,',
238 | 'still need to be set by users afterwards.)')
239 | )),
240 |
241 | # Display interactive parameter table
242 | rHandsontableOutput("AnalysisMod"),
243 |
244 | # Add buttons for various functions (see server() below for details)
245 | actionButton(inputId = "tab3to2",
246 | label = "Back to Step 2 (Values are Saved)"),
247 | actionButton(inputId = "autoRes",
248 | label = "Set Residual Variances for Me"),
249 | actionButton(inputId = "tab3to4",
250 | label = "Confirm Parameter Values"),
251 |
252 | # Display warning on model detection
253 | textOutput("step3_model_warning"),
254 |
255 | # Display warning on parameter table dimension
256 | textOutput("step3_dim_warning"),
257 |
258 | # Display warning on positive definite matrix
259 | textOutput("resid_warning"),
260 |
261 | # Display warning on standardized metric
262 | textOutput("resid_std"),
263 |
264 | # Display success on setting residuals
265 | textOutput("resid_success"),
266 |
267 | # Display warning on parameter selection
268 | textOutput("step3_para_warning"),
269 |
270 | # Display warning on parameter values
271 | textOutput("step3_para_all"),
272 |
273 | # Display success on parameter selection
274 | textOutput("step3_para_success")
275 |
276 | ),
277 |
278 |
279 | # *--- Step 4 -------------------------------------------------------------
280 |
281 | tabPanel(
282 | "4. Estimate Power", value = "tab4",
283 | br(),
284 | column(12,
285 | wellPanel(
286 |
287 | # Simulation setup
288 | fluidRow(
289 | column(4,
290 | numericInput(inputId = "sampleN",
291 | label = "Set your sample size",
292 | value = 200, min = 1, step = 1)),
293 | column(4,
294 | numericInput(inputId = "p_alpha",
295 | label = "Set your alpha level",
296 | value = .05, min = .001, max = 1)),
297 | column(4,
298 | numericInput(inputId = "seed",
299 | label = "Set seed for simulations",
300 | value = 42))),
301 | sliderInput(inputId = "ksim",
302 | label = "Set number of simulations",
303 | value = 100, min = 100, step = 100,
304 | max = 10000),
305 | helpText('We recommend starting with a low number of',
306 | 'simulations (e.g., 100) to get a rough',
307 | 'estimate of power before confirming it',
308 | 'with a higher number of simulations',
309 | '(e.g., 1000). The larger the number,',
310 | 'the longer simulations will take.'),
311 |
312 | # Add button to initiate simulations
313 | actionButton(inputId = "sim",
314 | label = "Estimate Power via Simulations")
315 | )
316 | ),
317 |
318 | # Display warning on model detection
319 | textOutput("step4_model_warning"),
320 |
321 | # Display warning on parameter table dimension
322 | textOutput("step4_dim_warning"),
323 |
324 | # Display warning on parameter selection
325 | textOutput("step4_para_warning"),
326 |
327 | # Display warning on parameter values
328 | textOutput("step4_para_all"),
329 |
330 | # Display results of simulations
331 | div(tableOutput("power"), style = "font-size:120%"),
332 |
333 | textOutput("powertable_note"),
334 |
335 | column(10,
336 | br(), br(),
337 |
338 | # Display histograms
339 | uiOutput("histograms"),
340 |
341 | plotOutput("histop"),
342 | textOutput("histop_note"),
343 | br(), br(),
344 | plotOutput("histoparam"),
345 | textOutput("histoparam_note")
346 | )
347 | ),
348 |
349 |
350 | # *--- Help ---------------------------------------------------------------
351 |
352 | tabPanel(
353 | "Help", value = "tab5",
354 | br(),
355 |
356 | # Factor loading
357 | h4("What factor loading strength should I specify?"),
358 | p("If you have a measure with a known reliability estimate",
359 | "(e.g., Cronbach's alpha), you can estimate the average factor",
360 | "loading strength of individual items in that measure using the",
361 | "Spearman-Brown prophecy formula with the calculator below."),
362 |
363 | # Set up Spearman-Brown calculator
364 | wellPanel(
365 |
366 | # Inputs
367 | fluidRow(
368 | column(9,
369 | sliderInput(
370 | "alpha",
371 | label = paste("Reliability estimate of measure",
372 | "(e.g., Cronbach's alpha)"),
373 | min = 0, max = .99, value = .80)),
374 | column(3,
375 | numericInput("nitem",
376 | label = "Number of items",
377 | min = 1, value = 3))
378 | ),
379 |
380 | # Output
381 | verbatimTextOutput("lambda.est")
382 | ),
383 |
384 | # Structural effect size
385 | h4("What structural effect sizes should I specify?"),
386 | p("The effect size of a structural parameter (e.g., regression",
387 | "coefficient between two latent factors) in SEM is often",
388 | "different from the effect size estimated from regressions using",
389 | "observed variables, because the structural parameter estimate",
390 | "could be disattenuated from measurement error. For example, if",
391 | "prior research found a correlation of .3 between two raw scores,",
392 | "the effect size of the corresponding true scores is likely larger",
393 | "(though note this is not necessarily the case if the effect size",
394 | "from observed variables is estimated in a multivariate path",
395 | "model; see e.g., Cole & Preacher, 2014). The calculator below",
396 | "allows you to disattenuate the effect size (in correlation)",
397 | "between two observed variables using the Spearman's correction."
398 | ),
399 |
400 | # Set up Spearman's correction calculator
401 | wellPanel(
402 |
403 | # Inputs
404 | sliderInput("raw.corr",
405 | label = "Correlation between observed variables A and B",
406 | min = 0, max = .99, value = .30),
407 | fluidRow(
408 | column(6,
409 | sliderInput("reliability1",
410 | label = "Reliability of variable A",
411 | min = 0, max = .99, value = .80)),
412 | column(6,
413 | sliderInput("reliability2",
414 | label = "Reliability of variable B",
415 | min = 0, max = .99, value = .80))),
416 |
417 | # Output
418 | verbatimTextOutput("latent.corr")
419 | )
420 | ),
421 |
422 |
423 | # *--- Resources ----------------------------------------------------------
424 |
425 | tabPanel(
426 | "Resources", value = "tab6",
427 | br(),
428 |
429 | # App Tutorials
430 | h4("App Tutorials"),
431 | tags$div(
432 | HTML(paste(
433 | tags$a(href = "https://psyarxiv.com/pj67b",
434 | "Wang and Rhemtulla (in press)"),
435 | ' includes a tutorial of pwrSEM using a simple mediation model.',
436 | sep = "")),
437 | style = "padding-bottom: 10px;"
438 | ),
439 |
440 | tags$div(
441 | HTML(paste(
442 | 'The ',
443 | tags$a(href = "https://osf.io/6m7xz/",
444 | "supplemental material"),
445 | ' includes an additional tutorial using a more complex model ',
446 | '(based on Zimmerman, Bandura, & Martinez-Pons, 1992).',
447 | sep = "")),
448 | style = "padding-bottom: 10px;"
449 | ),
450 |
451 | # Power to detect model misspecification
452 | h4("Power to Detect Model Misspecification"),
453 | p("Although not the focus of this Shiny app, we recognize that",
454 | "researchers may also want to conduct power analysis to detect",
455 | "model misspecification. As argued in our paper and elsewhere",
456 | "both types of power are important to sample size planning in SEM.",
457 | "Power analysis to detect model misspecification can be conducted",
458 | "with a variety of fit indices as effect sizes. The most popular",
459 | "approach by MacCallum, Browne, and Sugawara (1996) uses RMSEA and",
460 | "can be implemented via the calculator below."),
461 |
462 | # Set up MBS calculator
463 | wellPanel(
464 |
465 | # Inputs
466 | fluidRow(
467 | column(6,
468 | sliderInput("RMSEAnull", label = "Null RMSEA",
469 | min = 0, max = 0.20, step = .01,
470 | value = 0.05)),
471 | column(6,
472 | sliderInput("RMSEAalt", label = "Alternative RMSEA",
473 | min = 0, max = 0.20, step = .01,
474 | value = 0.10))
475 | ),
476 | fluidRow(
477 | column(3,
478 | numericInput("RMSEAalpha", label = "Alpha level",
479 | value = .05, min = .001, max = .999,
480 | step = .001)),
481 | column(3,
482 | numericInput(inputId = "df", label = "Degrees of freedom",
483 | value = 1, min = 1, step = 1)),
484 | column(3,
485 | numericInput(inputId = "RMSEAn", label = "Sample size",
486 | value = 200, min = 1, step = 1)),
487 |
488 | # Output
489 | column(3,
490 | verbatimTextOutput("RMSEApower"))
491 | )
492 | ),
493 |
494 | # Note on the Satorra-Saris approach
495 | tags$div(
496 | HTML(paste(
497 | "Alternatively, power to detect model misspecification ",
498 | "can be calculated with Satorra and Saris' (1985) approach, ",
499 | "which uses Chi-square likelihood-ratio. We direct interested ",
500 | "researchers to ",
501 | tags$a(href = "https://webpower.psychstat.org/models/sem02/",
502 | "its implementation"),
503 | " in WebPower (Zhang & Yuan, 2018).",
504 | sep = "")
505 | )
506 | ),
507 | br(),
508 |
509 | # Learning resources
510 | h4("Resources for Learning SEM"),
511 | tags$ul(
512 | tags$li(HTML(paste('Kline, R. B. (2016).', tags$i(
513 | 'Principles and practice of structural equation modeling'),
514 | "(4th ed.). Guilford Press, New York, NY.", sep = " ")
515 | )
516 | ),
517 | tags$li(tags$a(
518 | href = "http://lavaan.ugent.be/resources/teaching.html",
519 | "Teaching materials for lavaan")
520 | ),
521 | tags$li(tags$a(
522 | href = paste("https://curranbauer.org/wp-content/uploads/",
523 | "2019/04/SEM-R-notes-2019-3.pdf", sep = ""),
524 | paste("Structural Equation Modeling R Demonstration Notes, by",
525 | "Daniel J. Bauer and Patrick J. Curran", sep = " "))
526 | )
527 | )
528 |
529 | )
530 | )
531 | )
532 | )
533 | )
534 |
535 |
536 |
537 | # Define server logic -----------------------------------------------------
538 |
539 | server <- function(input, output, session) {
540 |
541 |
542 | # Assign reactive values from boolean inputs ------------------------------
543 |
544 | # Radio button in Step 1
545 | stdlv <- reactive({
546 | if (input$stdlv.radio == 1) {stdlv <- TRUE} else {stdlv <- FALSE}
547 | })
548 |
549 | # Radio button in Step 2
550 | structural <- reactive({
551 | if (input$structural == 1) {structural <- FALSE} else {structural <- TRUE}
552 | })
553 |
554 |
555 | # Store outputs of calculators --------------------------------------------
556 |
557 | # Estimated factor loading output
558 | output$lambda.est <- renderText({
559 | paste0("Estimated average factor loading per item: ",
560 | round(sqrt(input$alpha/(input$nitem + (1 - input$nitem)*input$alpha)
561 | ), 2)
562 | )
563 | })
564 |
565 | # Estimated structural effect size output
566 | output$latent.corr <- renderText({
567 | paste0("Estimated correlation between latent variables A and B: ",
568 | round(input$raw.corr/(sqrt(input$reliability1*input$reliability2)
569 | ), 2)
570 | )
571 | })
572 |
573 | # Power to detect model misspecification output
574 | output$RMSEApower <- renderText({
575 | paste0("Power: ", round(
576 | findRMSEApower(rmsea0 = input$RMSEAnull,
577 | rmseaA = input$RMSEAalt,
578 | df = input$df, n = input$RMSEAn,
579 | alpha = input$RMSEAalpha),
580 | 3)
581 | )
582 | })
583 |
584 |
585 | # Events reactive to cross-tab navigation buttons ------------------------
586 |
587 | observeEvent(input$tab2to1, {
588 | updateTabsetPanel(session, "tabby", selected = "tab1")
589 | })
590 |
591 | observeEvent(input$tab2to3, {
592 | updateTabsetPanel(session, "tabby", selected = "tab3")
593 | })
594 |
595 | observeEvent(input$tab3to2, {
596 | updateTabsetPanel(session, "tabby", selected = "tab2")
597 | })
598 |
599 |
600 | # Events reactive to "Set Model" in Step 1 --------------------------------
601 |
602 | # Assign reactive object
603 | mg <- eventReactive(input$clicks1, {
604 |
605 | # Simulate data for visualization
606 | vis_dat <- as.data.frame(simulateData(input$text1, sample.nobs = 1000),
607 | empirical = T)
608 |
609 | # Generate fitted model for diagram display
610 | vis_fit <- sem(model = input$text1, data = vis_dat, std.lv = stdlv())
611 |
612 | # Generate parameter table
613 | am <- parameterTable(vis_fit)[, c(1:4, 8:9, 11)]
614 | am$effect <- FALSE # By default, no parameter is selected in "Effect"
615 |
616 | # Identify row numbers with different parameter types
617 |
618 | # regression coefficient
619 | am_idRG <- which(am$op == "~")
620 |
621 | # factor loading
622 | am_idMR <- which(am$op == "=~")
623 | # total variance
624 | am_idTV <- which(am$op == "~~" & am$lhs == am$rhs &
625 | am$lhs %in% lavNames(input$text1, type = "lv.x"))
626 | # residual variance
627 | am_idRV <- which(am$op == "~~" & am$lhs == am$rhs &
628 | !(am$lhs %in% lavNames(input$text1, type = "lv.x")))
629 | # covariance
630 | am_idTC <- which(am$op == "~~" & am$lhs != am$rhs &
631 | am$lhs %in% lavNames(input$text1, type = "lv.x"))
632 | # residual covariance
633 | am_idRC <- which(am$op == "~~" & am$lhs != am$rhs &
634 | !(am$lhs %in% lavNames(input$text1, type = "lv.x")))
635 | # intercept
636 | am_idIT <- which(am$op == "~1")
637 |
638 | # labelled parameter
639 | am_idLB <- which(am$op == ":=")
640 |
641 | # Add description of each parameter by type
642 | am$description <- NA
643 | am$description[am_idRG] <- paste(am$lhs[am_idRG], "is regressed on",
644 | am$rhs[am_idRG], sep = " ")
645 | am$description[am_idMR] <- paste(am$lhs[am_idMR], "is measured by",
646 | am$rhs[am_idMR], sep = " ")
647 | am$description[am_idTV] <- paste("Total variance of", am$lhs[am_idTV],
648 | sep = " ")
649 | am$description[am_idRV] <- paste("Residual variance of", am$lhs[am_idRV],
650 | sep = " ")
651 | am$description[am_idTC] <- paste("Variance of", am$lhs[am_idTC],
652 | "covaries with variance of",
653 | am$rhs[am_idTC], sep = " ")
654 | am$description[am_idRC] <- paste("Residual of", am$lhs[am_idRC],
655 | "covaries with residual of",
656 | am$rhs[am_idRC], sep = " ")
657 | am$description[am_idIT] <- paste("Intercept of", am$lhs[am_idIT], sep = " ")
658 | am$description[am_idLB] <- "Labelled parameter"
659 |
660 | # Display parameter type
661 | am$type <- NA
662 | am$type[am_idRG] <- "regression coefficient"
663 | am$type[am_idMR] <- "factor loading"
664 | am$type[am_idTV] <- "total variance"
665 | am$type[am_idRV] <- "residual variance"
666 | am$type[am_idTC] <- "covariance"
667 | am$type[am_idRC] <- "residual covariance"
668 | am$type[am_idIT] <- "intercept"
669 | am$type[am_idLB] <- "labelled parameter"
670 |
671 | # Make table more readable
672 | am <- tidyr::unite(am, "parameter", lhs:rhs, sep = " ")
673 | am <- am[, c(1, 2, 5, 7, 4, 8, 6, 3)]
674 | names(am) <- c("Row", "Parameter", "Label", "Description", "Value", "Type",
675 | "Effect", "Free")
676 |
677 | # Return parameter table and diagram
678 | return(list(am, vis_fit))
679 |
680 | })
681 |
682 |
683 | observeEvent(input$clicks1, {
684 |
685 | # Render interactive parameter table
686 | output$AnalysisMod <- renderRHandsontable({
687 |
688 | # Set table dimensions
689 | rhandsontable(mg()[[1]], rowHeaders = NULL, stretchH = "all",
690 | height = 300) %>%
691 |
692 | # Set all columns other than "Value" to read-only
693 | hot_col(col = c("Row", "Parameter", "Label", "Description", "Type",
694 | "Free"), readOnly = T) %>%
695 |
696 | # Highlight cell selection
697 | hot_table(highlightCol = T, highlightRow = T) %>%
698 |
699 | # Disable row and column editing
700 | hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE)
701 | })
702 |
703 |
704 | # Reader diagram
705 | output$plot <- renderPlot({
706 | semPaths(mg()[[2]], edge.color = 'black', curvature = 3,
707 | structural = structural(), sizeMan = input$sizeMan,
708 |
709 | # Include visualization options
710 | sizeLat = input$sizeLat, rotation = input$rotation)
711 | })
712 |
713 | # Direct users to Step 2
714 | updateTabsetPanel(session, "tabby", selected = "tab2")
715 |
716 | # Clear out warning messages
717 | output$step3_para_warning <- output$step3_para_all <-
718 | output$step3_dim_warning <- output$step3_para_success <-
719 | output$step3_model_warning <- output$step4_para_warning <-
720 | output$step4_para_all <- output$step4_model_warning <-
721 | output$step4_dim_warning <- renderText("")
722 | })
723 |
724 |
725 | # Events reactive to "Set Residual Variances for Me" in Step 3 ------------
726 |
727 | observeEvent(input$autoRes, {
728 |
729 | # Test if model is entered
730 | test_model_enter <- try(parameterTable(mg()[[2]]), silent = T)
731 |
732 | if (inherits(test_model_enter, "try-error")) {
733 |
734 | output$step3_model_warning <- renderText(
735 | "No model detected. Did you enter a model in Step 1?")
736 |
737 | } else {
738 |
739 | # Create one parameter table that we use later to obtain psi matrix
740 | text1.t <- parameterTable(mg()[[2]])
741 | text1.t$free <- 0
742 |
743 | # Test if dimensions of entered parameter value is correct
744 | # (e.g., if users copy a longer column from Excel to the app)
745 | test_model_dim <- try(
746 | text1.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"], silent = T)
747 |
748 | if (inherits(test_model_dim, "try-error")) {
749 |
750 | output$step3_dim_warning <- renderText(paste(
751 | "Incorrect dimensions of parameter table. Please regenerate",
752 | "the parameter table by resetting the model in Step 1.", sep = " "))
753 |
754 | } else {
755 |
756 | # Receive parameter values that users input
757 | text1.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"]
758 | text1.t[is.na(text1.t[, "ustart"] == T), "ustart"] <- 1
759 | text1.t <- text1.t[(text1.t$op) != ":=", ] # Exclude labelled parameter
760 |
761 | # Create another parameter table that we use later for other matrices
762 | PopMod.t <- parameterTable(mg()[[2]])
763 | PopMod.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"]
764 | PopMod.t[is.na(PopMod.t[, "ustart"] == T), "ustart"] <- 0
765 | PopMod.t <- PopMod.t[(PopMod.t$op) != ":=", ] # Exclude labelled parameter
766 |
767 | # Test model-implied covariance matrix
768 | text1.t <- text1.t[, c("id", "lhs", "op", "rhs", "user", "block", "group",
769 | "free", "ustart", "exo", "label", "plabel")]
770 | test_dat_sT <- try(simulateData(text1.t, empirical = T, standardized = T,
771 | sample.nobs = 1000), silent = T)
772 |
773 | if (inherits(test_dat_sT, "try-error")) {
774 |
775 | output$resid_warning <- renderText(paste(
776 | "Residual variances could not be calculated. Make sure the parameter",
777 | "values you entered can produce a positive definite model-implied",
778 | "covariance matrix.", seq = " ")
779 | )
780 | output$resid_std <- output$resid_success <- renderText("")
781 |
782 | } else {
783 |
784 | # Simulate data to obtain model-implied covariance matrix later
785 | dat_sF <- simulateData(text1.t, empirical = T, standardized = F,
786 | sample.nobs = 1000)
787 |
788 |
789 | # Test if values are entered in standardized metric
790 | if (TRUE %in% (abs(hot_to_r(input$AnalysisMod)$Value) > 1)) {
791 |
792 | output$resid_std <- renderText(paste(
793 | "Residual variances could not be calculated. To automatically set",
794 | "residual variances, make sure the parameters you enter are in",
795 | "standardized metric.", seq = " ")
796 | )
797 | output$resid_warning <- output$resid_success <- renderText("")
798 |
799 | # Test if model-implied covariance matrix is positive definite
800 | } else if (
801 | inherits(try(sem(input$text1, dat_sF, std.lv = T), silent = T),
802 | "try-error")) {
803 |
804 | output$resid_warning <- renderText(paste(
805 | "Residual variances could not be calculated. Make sure the",
806 | "parameter values you entered can produce a positive definite",
807 | "model-implied covariance matrix."))
808 | output$resid_std <- output$resid_success <- renderText("")
809 |
810 | } else {
811 |
812 | # Extract baseline matrices
813 | fit_sF <- sem(input$text1, dat_sF, std.lv = T)
814 | psi <- inspect(fit_sF, "coef")$psi
815 | beta <- inspect(fit_sF, "coef")$beta
816 | lambda <- inspect(fit_sF, "coef")$lambda
817 | I <- diag(dim(beta)[1])
818 |
819 | # Solve baseline covariance matrix of latent variables (not correct yet)
820 | sig <- solve(I - beta) %*% psi %*% solve(I - t(beta))
821 | diag(sig) <- 1
822 |
823 | # Solve for correct psi matrix
824 | psi <- (I - beta) %*% sig %*% (I - t(beta))
825 |
826 | psi2 <- diag(diag(psi))
827 | sig2 <- solve(I - beta) %*% psi2 %*% solve(I - t(beta))
828 | diag(sig2) <- 1
829 | psi <- (I - beta) %*% sig2 %*% (I - t(beta))
830 |
831 | while(sum(round(psi[lower.tri(psi)], 10)) != 0){
832 | psi2 <- diag(diag(psi))
833 | sig2 <- solve(I - beta) %*% psi2 %*% solve(I - t(beta))
834 | diag(sig2) <- 1
835 | psi <- (I - beta) %*% sig2 %*% (I - t(beta))
836 | }
837 |
838 | # Save and label residual values
839 | theta <- diag(dim(lambda)[1]) - diag(diag(lambda %*% sig2 %*% t(lambda)))
840 | rownames(theta) <- colnames(theta) <- rownames(lambda)
841 |
842 | # Assign values from theta (note that it only supplies correct residuals
843 | # of indicators; the rest will be overwritten by psi matrix next)
844 | PopMod.t$ustart[which(PopMod.t$lhs == PopMod.t$rhs & PopMod.t$op == "~~" &
845 | PopMod.t$lhs %in% names(diag(theta)))] <- diag(theta)
846 |
847 | # Identify rows with residuals from psi matrix
848 | res_psi <- which(PopMod.t$lhs == PopMod.t$rhs & PopMod.t$op == "~~" &
849 | PopMod.t$lhs %in% names(diag(psi)))
850 |
851 | # Assign values from psi back to parameter table
852 | PopMod.t$ustart[res_psi][order(
853 | match(PopMod.t$lhs[res_psi], names(diag(psi))))] <- diag(psi)
854 |
855 | # Create parameter table with calculated residuals
856 | mg_sr <- mg()[[1]]
857 |
858 | if (dim(mg_sr)[1] == length(PopMod.t$ustart)) {
859 |
860 | mg_sr$Value <- PopMod.t$ustart
861 |
862 | } else {
863 |
864 | mg_sr$Value <- c(
865 | PopMod.t$ustart, rep(
866 | NA, abs(dim(mg_sr)[1] - length(PopMod.t$ustart))
867 | ))
868 |
869 | }
870 |
871 | # Render interactive parameter table again with all residuals set
872 | output$AnalysisMod <- renderRHandsontable({
873 | rhandsontable(mg_sr, rowHeaders = NULL, stretchH = "all", height = 300) %>%
874 | hot_col(col = c("Row", "Parameter", "Label", "Description",
875 | "Type", "Free"), readOnly = T) %>%
876 | hot_table(highlightCol = T, highlightRow = T) %>%
877 | hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE)
878 | })
879 |
880 | output$resid_success <-
881 | renderText("Residual variances are automatically set.")
882 | output$resid_std <- output$resid_warning <-
883 | output$step3_para_all <- renderText("")
884 |
885 | }
886 |
887 | }
888 |
889 | }
890 |
891 | }
892 | }
893 | )
894 |
895 |
896 | # Events reactive to "Confirm Parameter Values" in Step 3 -----------------
897 |
898 | observeEvent(input$tab3to4, {
899 |
900 | test_model_enter <- try(parameterTable(mg()[[2]]), silent = T)
901 |
902 | # Test if model is entered
903 | if (inherits(test_model_enter, "try-error")) {
904 |
905 | output$step3_model_warning <- renderText(
906 | "No model detected. Did you enter a model in Step 1?")
907 |
908 | } else {
909 |
910 | text1.t <- parameterTable(mg()[[2]])
911 | text1.t$free <- 0
912 | test_model_dim <- try(
913 | text1.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"], silent = T)
914 |
915 |
916 | # Test if parameter table has correct dimensions
917 | if (inherits(test_model_dim, "try-error")) {
918 |
919 | output$step3_dim_warning <- renderText(paste(
920 | "Incorrect dimensions of parameter table. Please regenerate",
921 | "the parameter table by resetting the model in Step 1.", sep = " "))
922 | output$step3_model_warning <-
923 | renderText("")
924 |
925 | # Test if all parameters are specified
926 | } else if (TRUE %in% is.na(hot_to_r(input$AnalysisMod)$Value)) {
927 |
928 | output$step3_para_all <-
929 | renderText("All parameter values need to be specified.")
930 | output$step3_model_warning <- output$step3_dim_warning <-
931 | output$step3_para_warning <- output$step3_para_success <-
932 | output$step4_para_warning <- renderText("")
933 |
934 | # Test if at least one target effect is selected
935 | } else if (!(TRUE %in% hot_to_r(input$AnalysisMod)$Effect)) {
936 |
937 | output$step3_para_warning <-
938 | renderText("Please select at least one parameter as the target effect.")
939 | output$step3_model_warning <- output$step3_dim_warning <-
940 | output$step3_para_success <- output$step3_para_all <-
941 | output$step4_para_all <- renderText("")
942 |
943 | } else {
944 |
945 | PopMod.t <- parameterTable(mg()[[2]])
946 | PopMod.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"]
947 |
948 |
949 | # Test if model-implied covariance matrix is positive definite
950 | if (inherits(try(simulateData(PopMod.t, sample.nobs = input$sampleN),
951 | silent = T), "try-error")) {
952 |
953 | output$step3_model_warning <- renderText(paste(
954 | "Your model-implied covariance matrix is not positive definite.",
955 | "Make sure the parameter values you enter can produce a positive",
956 | "definite model-implied covariance matrix."))
957 |
958 | # Confirm parameter values
959 | } else {
960 |
961 | updateTabsetPanel(session, "tabby", selected = "tab4")
962 | output$step3_para_success <-
963 | renderText("Parameter values confirmed.")
964 | output$step3_model_warning <- output$step3_dim_warning <-
965 | output$step3_para_warning <- output$step3_para_all <-
966 | output$step4_para_warning <- output$step4_para_all <-
967 | renderText("")
968 | }
969 | }
970 | }
971 | }
972 | )
973 |
974 | # Events reactive to "Estimate Power via Simulations" in Step 4 -----------
975 |
976 | observeEvent(input$sim, {
977 |
978 | test_model_enter <- try(parameterTable(mg()[[2]]), silent = T)
979 |
980 | # Test if model is entered
981 | if (inherits(test_model_enter, "try-error")) {
982 | output$step4_model_warning <- renderText(
983 | "No model detected. Did you enter a model in Step 1?")
984 |
985 | } else {
986 |
987 | text1.t <- parameterTable(mg()[[2]])
988 | text1.t$free <- 0
989 |
990 | test_model_dim <- try(
991 | text1.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"], silent = T)
992 |
993 |
994 | # Test if parameter table has correct dimensions
995 | if (inherits(test_model_dim, "try-error")) {
996 |
997 | output$step4_dim_warning <- renderText(paste(
998 | "Incorrect dimensions of parameter table. Please regenerate",
999 | "the parameter table by resetting the model in Step 1.", sep = " "))
1000 | output$step4_model_warning <-
1001 | renderText("")
1002 |
1003 | # Test if all parameters are specified
1004 | } else if (
1005 | TRUE %in% is.na(hot_to_r(input$AnalysisMod)$Value)) {
1006 |
1007 | output$step4_para_all <-
1008 | renderText("All parameter values need to be specified.")
1009 | output$step4_para_warning <-
1010 | renderText("")
1011 |
1012 | # Test if at least one target effect is selected
1013 | } else if (!(TRUE %in% hot_to_r(input$AnalysisMod)$Effect)) {
1014 |
1015 | output$step4_para_warning <-
1016 | renderText("Please select at least one parameter as the target effect.")
1017 | output$step4_para_all <-
1018 | renderText("")
1019 |
1020 | } else {
1021 |
1022 | PopMod.t <- parameterTable(mg()[[2]])
1023 | PopMod.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"]
1024 |
1025 |
1026 | # Test if model-implied covariance matrix is positive definite
1027 | if (inherits(try(simulateData(PopMod.t, sample.nobs = input$sampleN),
1028 | silent = T), "try-error")) {
1029 |
1030 | output$step4_para_warning <- renderText(paste(
1031 | "Simulations could not be run because the model-implied covariance",
1032 | "matrix is not positive definite. Make sure the parameter values",
1033 | "you entered in Step 3 can produce a positive definite model-implied",
1034 | "covariance matrix."))
1035 |
1036 | } else {
1037 |
1038 | output$step4_para_warning <- output$step4_para_all <- renderText("")
1039 |
1040 | # Create dataframe to store results
1041 | results <- NULL
1042 |
1043 | # Set simulation seed based on user input
1044 | set.seed(input$seed)
1045 |
1046 | # Define population model and target parameter based on user input
1047 | PopMod.t <- parameterTable(mg()[[2]])
1048 | PopMod.t[, "ustart"] <- hot_to_r(input$AnalysisMod)[, "Value"]
1049 | PopMod.t <- PopMod.t[, c(
1050 | "id", "lhs", "op", "rhs", "user", "block", "group", "free",
1051 | "ustart", "exo", "label", "plabel")]
1052 | target <- which(hot_to_r(input$AnalysisMod)$Effect == TRUE)
1053 |
1054 | # Set progress bar
1055 | withProgress(message = 'Simulating', value = 0, {
1056 |
1057 | # Loop by iteration
1058 | for (i in 1:input$ksim) {
1059 |
1060 | # Simulate and store data based on sample size input
1061 | data <- simulateData(PopMod.t, sample.nobs = input$sampleN)
1062 | data <- as.data.frame(data)
1063 |
1064 | # Fit analysis model to data
1065 | fit <- sem(model = input$text1, data = data, std.lv = stdlv())
1066 |
1067 | # Store parameter row
1068 | results <- rbind(results, parameterEstimates(fit)[target, ])
1069 |
1070 | # Display progress bar
1071 | incProgress(1/input$ksim, detail = paste("sample", i, "of",
1072 | input$ksim))
1073 | }
1074 |
1075 | # Convergence rate
1076 | conv <- (input$ksim - sum(is.na(results$pvalue)))/input$ksim
1077 |
1078 | # Create placeholder powertable and CI tables
1079 | powertable <- as.data.frame(matrix(NA, nrow = length(target), ncol = 5))
1080 | colnames(powertable) <- c("Parameter", "Value", "Median", "Power",
1081 | "Power (All Cases)")
1082 |
1083 | ci_table <- as.data.frame(matrix(NA, nrow = length(target), ncol = 3))
1084 | colnames(ci_table) <- c("Parameter", "est.ci.lower", "est.ci.upper")
1085 |
1086 | # add parameter column in results for later identification
1087 | results$Parameter <- paste(results$lhs, results$op,
1088 | results$rhs, sep = " ")
1089 |
1090 | lapply(1:length(target), function(i) {
1091 |
1092 | # row names of results for a given parameter
1093 | ii <- seq(from = i, to = dim(results)[1], by = length(target))
1094 |
1095 | # row names of results for a given parameter with non-NA estimates
1096 | ii.est <- which(is.na(results$est) == F)[which(
1097 | is.na(results$est) == F) %in% ii]
1098 |
1099 | # dataframe with non-NA estimates
1100 | results.est <- results[ii.est, ]
1101 |
1102 | # lower and upper bounds of 95% of non-NA parameter estimates
1103 |
1104 | if (round(length(results.est$est) * 0.025) == 0) {
1105 | est.ci.lower <- "inf"
1106 | } else {
1107 | est.ci.lower <- round(sort(results.est$est)[
1108 | length(results.est$est) * 0.025], 2)
1109 | }
1110 |
1111 | if (round(length(results.est$est) * 0.975) == 0) {
1112 | est.ci.upper <- "inf"
1113 | } else {
1114 | est.ci.upper <- round(sort(results.est$est)[
1115 | length(results.est$est) * 0.975], 2)
1116 | }
1117 |
1118 |
1119 | # number of iterations with significant p-values
1120 | n_sig <- length(which(results[ii, ]$pvalue <= input$p_alpha))
1121 |
1122 | # power (denominator = # all iterations)
1123 | power <- n_sig/(conv * input$ksim)
1124 |
1125 | # power (denominator = # all iterations)
1126 | powerksim <- n_sig/input$ksim
1127 |
1128 | # variance of power across simulations
1129 | n_sig_var <- power * conv * input$ksim * (1 - power)
1130 |
1131 | # print power table
1132 | powertable[i, "Parameter"] <<- results[i, "Parameter"]
1133 | powertable[i, "Value"] <<- hot_to_r(input$AnalysisMod)$Value[target[i]]
1134 | powertable[i, "Median"] <<- median(results[ii, ]$est)
1135 | powertable[i, "Power"] <<- power
1136 | powertable[i, "Power (All Cases)"] <<- powerksim
1137 |
1138 | # print CI table
1139 | ci_table[i, "Parameter"] <<- results.est[i, "Parameter"]
1140 | ci_table[i, "est.ci.lower"] <<- est.ci.lower
1141 | ci_table[i, "est.ci.upper"] <<- est.ci.upper
1142 |
1143 | })
1144 |
1145 |
1146 | # Render table of power analysis results
1147 | output$power <- renderTable({
1148 | powertable
1149 | }, digits = 2, align = "l")
1150 |
1151 | # Add note on power based on convergence rate
1152 | output$powertable_note <- renderText({
1153 | paste('Convergence rate is ', round(conv, 3), '. ',
1154 | 'Value is the population parameter value as set in Step 3. ',
1155 | 'Median is the median of simulated estimates of a parameter. ',
1156 | 'Power is estimated from all simulations with converged ',
1157 | 'models. Power (All Cases) is estimated from all ',
1158 | 'simulations, including those with non-converged models ',
1159 | '(which had no parameter estimates and were counted as ',
1160 | 'failure to reject the null).',
1161 | sep = "")
1162 | })
1163 |
1164 | # Select parameter for histogram displays
1165 | output$histograms <- renderUI(
1166 | selectInput("para_hist",
1167 | label = "Select parameter to display histograms",
1168 | choices = powertable$Parameter)
1169 | )
1170 |
1171 | # Render histogram of p-values
1172 | output$histop <- renderPlot({
1173 | hist(results[results$Parameter == input$para_hist, ]$pvalue,
1174 | breaks = 50,
1175 | col = "#75dbd9", border = "white",
1176 | xlab = "p-values of the Estimated Parameter",
1177 | ylab = "Number of Simulated Samples",
1178 | main = "Histogram of Estimated p-Values",
1179 | xlim = c(0, 1))
1180 | abline(v = input$p_alpha, lwd = 2)
1181 | })
1182 |
1183 | # Footnote
1184 | output$histop_note <- renderText({
1185 | paste('Vertical solid line indicates alpha level.')
1186 | })
1187 |
1188 | # Render histogram of parameter estimates
1189 | output$histoparam <- renderPlot({
1190 | hist(results[results$Parameter == input$para_hist, ]$est, breaks = 100,
1191 | col = "#75AADB", border = "white",
1192 | xlab = "Estimated Parameter Value",
1193 | ylab = "Number of Simulated Samples",
1194 | main = "Histogram of Estimated Parameter Values")
1195 | abline(v = hot_to_r(input$AnalysisMod)$Value[which(
1196 | hot_to_r(input$AnalysisMod)$Parameter == input$para_hist)], lwd = 2)
1197 | abline(v = powertable$Median[which(
1198 | powertable$Parameter == input$para_hist)], lty = 3, lwd = 2)
1199 | })
1200 |
1201 | # Footnote
1202 | output$histoparam_note <- renderText({
1203 | paste('95% of parameter estimates fall within the interval [',
1204 | ci_table[ci_table$Parameter == input$para_hist, ]$est.ci.lower,
1205 | ', ',
1206 | ci_table[ci_table$Parameter == input$para_hist, ]$est.ci.upper,
1207 | ']. Vertical solid line ',
1208 | 'indicates the population value you set for the parameter; ',
1209 | 'vertical dotted line indicates the median of parameter ',
1210 | 'estimates from the simulated samples.', sep = "")
1211 | })
1212 | })
1213 | }
1214 |
1215 | }
1216 | }
1217 | }
1218 | )
1219 |
1220 | }
1221 |
1222 |
1223 | # Run the app -------------------------------------------------------------
1224 |
1225 | shinyApp(ui = ui, server = server)
1226 |
--------------------------------------------------------------------------------
/pwrSEM_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilinandrewang/pwrSEM/3ce3c1b669ce2b2fdba9401670095b08b5609ccf/pwrSEM_demo.gif
--------------------------------------------------------------------------------